Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Poetry build system (mainly so we don't have to commit dynamically generated front end bundles to git) #2725

Merged
merged 117 commits into from
Jul 5, 2024

Conversation

mquinnfd
Copy link
Contributor

@mquinnfd mquinnfd commented May 24, 2024

Switching to the Poetry build system

Tip

This PR was worked on iteratively in Draft form, so there's a bit of history in the comments here.

This is being suggested for integration to make building and bundling the front end a bit easier, comments welcome particularly if you have strong opinions on poetry

In a Nutshell

This PR changes the build system used to package Locust over to Poetry.

The main use-case for this currently, was to allow the removal of the (snazzy new) dynamically generated front end bundle from the actual source code, and just let the build process create these for the package during the build.

Caveat

Details

I've tried to keep the package output and metadata like-for-like, because I havn't looked behind the curtain at the Locust release process, CI runners, etc. - it's likely this may need to change for that purpose.

There's plenty I am blissfully unaware of when it comes to maintaining this project, this is just a jumping off point for the sake of conversation - I have no particular love of Poetry, though I do use this day-to-day for projects currently. Alternatives noted were PDM (and now Rye by "the Ruff people") but I have no idea if these support the king of things we're doing here currently.


What

Adds the Poetry build system to the Locust package, this changes the build backend to Poetry's PEP-517 compliant build back-end; Poetry Core

  • Added Poetry build system
  • Don't check in Poetry lock file, as we're often used as a library (worth discussion) - but do include it in generated builds, for provenance
  • Poetry plugin poetry-dynamic-versioning does the job of setuptools-scm to keep the version number in line with VCS info

Why

Discussed with @cyberw here: #2405 (comment)

  • Allows for pre-build script, which allows us to build the front end on-demand and remove it from VCS
  • While Poetry is far from perfect, it is at least well maintained by a large community
  • Package size reduced by 10% due to not packaging tests etc. in the wheel (were these necessary somehow? library users maybe?)

Try it Out

Local dev installation

Details
make install

  • Installs project dependencies
  • Installs editable local package of `locust`

Packaging

Details
make build

  • Installs `poetry` and the required plugin
  • Builds the project, including the web UI
  • Packages this, and homogenises the wheel package name

TODO

I wanted to put there out there for discussion before doing things like:

  • Rework the Github actions workflows for the CI build
  • Adding documentation and updating the README
  • Adding improvements to the build process
- Modify pyproject.toml file for poetry build system
- Adds build script to generate front end as part of poetry build
- Adds renaming script to remove architecture info from wheel file name
- Updates Makefile to call the correct build steps with `make build`
@mquinnfd
Copy link
Contributor Author

For the consideration of @cyberw and community, apologies on the delay, sometimes life happens 😓

Makefile Show resolved Hide resolved
pyproject.toml Outdated Show resolved Hide resolved
@mquinnfd
Copy link
Contributor Author

I'm also unsure if you have squash-on-merge turned on for the repo, just an FYI as I have not lovingly rebased my commits currently

@cyberw
Copy link
Collaborator

cyberw commented May 24, 2024

First run of make install failed for me. Probably the shell doesnt update its list of installed things in the middle of a && (could be enough to move poetry self add... to a new line in the Makefile)

...
Using cached jaraco.classes-3.4.0-py3-none-any.whl (6.8 kB)
Installing collected packages: jaraco.classes, cleo, build, keyring, cachecontrol, poetry-plugin-export, poetry
Successfully installed build-1.2.1 cachecontrol-0.14.0 cleo-2.1.0 jaraco.classes-3.4.0 keyring-24.3.1 poetry-1.8.3 poetry-plugin-export-1.8.0
/bin/sh: poetry: command not found
make: *** [setup_build] Error 127
@cyberw
Copy link
Collaborator

cyberw commented May 24, 2024

Perhaps it makes more sense to just assume poetry is installed (possibly giving a slightly nicer error message if it is not)

@cyberw
Copy link
Collaborator

cyberw commented May 24, 2024

Make build fails on yarn not being installed too. Bonus points if we can give a nice error message or even provide an action to install that.

@cyberw
Copy link
Collaborator

cyberw commented May 24, 2024

Make install doesnt actually add locust to my path. Do i need to do something more to set up poetry correctly? (python3 -m locust ... works though)

@cyberw
Copy link
Collaborator

cyberw commented May 24, 2024

I think the versioning is only almost right.

python -m locust -V
locust 2.28.0 from /Users/lars/git/locust/locust (Python 3.12.2, OpenSSL 3.3.0)

On an un-tagged master it should be something like locust-2.28.1.dev5 (this version is needed to make good versions for pre-releases that are made whenever something is merged to master, see https://pypi.org/project/locust/#history)

@mquinnfd
Copy link
Contributor Author

First run of make install failed for me. Probably the shell doesnt update its list of installed things in the middle of a && (could be enough to move poetry self add... to a new line in the Makefile)

...
Using cached jaraco.classes-3.4.0-py3-none-any.whl (6.8 kB)
Installing collected packages: jaraco.classes, cleo, build, keyring, cachecontrol, poetry-plugin-export, poetry
Successfully installed build-1.2.1 cachecontrol-0.14.0 cleo-2.1.0 jaraco.classes-3.4.0 keyring-24.3.1 poetry-1.8.3 poetry-plugin-export-1.8.0
/bin/sh: poetry: command not found
make: *** [setup_build] Error 127

Great shout! I ran this again in a clean venv and fixed with 3b55405

@mquinnfd
Copy link
Contributor Author

mquinnfd commented May 25, 2024

Make build fails on yarn not being installed too. Bonus points if we can give a nice error message or even provide an action to install that.

Yeah good idea, I don't think there's a reliable cross-platform way to do this really - I've added 9c90aec which gives the following behaviour when you don't have the yarn binary available:

<username>@<hostname> locust-fork % make build
Locust requires the yarn binary to be available in this shell to build the web front-end.
See: https://classic.yarnpkg.com/lang/en/docs/install
make: *** [check-yarn] Error 1

(I assume we're using classic yarn)

We could certainly do something like attempting to install it via NPM after checking if NPM is there?

  • If npm not installed, echo error -> exit
  • If yarn not installed, npm install yarn etc. -> continue
  • If poetry not installed, pip install poetry etc. -> continue
  • (build happens)

Same goes with poetry - whatever is easier for people, could just warn out to the terminal alone.

@mquinnfd
Copy link
Contributor Author

mquinnfd commented May 25, 2024

Make install doesnt actually add locust to my path. Do i need to do something more to set up poetry correctly? (python3 -m locust ... works though)

Would you mind trying again on a clean venv if possible? (with the branch up to date)
I added 7e290ff which, for me, results in:

  • make install
  • locust is on the path, and can be called
@mquinnfd
Copy link
Contributor Author

I think the versioning is only almost right.

python -m locust -V
locust 2.28.0 from /Users/lars/git/locust/locust (Python 3.12.2, OpenSSL 3.3.0)

On an un-tagged master it should be something like locust-2.28.1.dev5 (this version is needed to make good versions for pre-releases that are made whenever something is merged to master, see https://pypi.org/project/locust/#history)

I'll need to take a look at this behaviour - it could be that I've made a load of tags testing this and something's getting a bit confused, I can try on a clean branch maybe - it should reflect your latest local git tag though right? What is that, in your case?

user@computer % python -m locust -V
locust 2.28.0.dev0 from /Users/user/dev/locust-fork/locust (Python 3.9.16, OpenSSL 1.1.1v)
user@computer % git describe --tags --abbrev=0
2.28.0.dev0
@cyberw
Copy link
Collaborator

cyberw commented May 25, 2024

Make build fails on yarn not being installed too. Bonus points if we can give a nice error message or even provide an action to install that.

Yeah good idea, I don't think there's a reliable cross-platform way to do this really - I've added 9c90aec which gives the following behaviour when you don't have the yarn binary available:

I think this is a reasonable compromise. People may want to install node and yarn in various ways so automating that is out of scope I think.

.SILENT:
.PHONY: check-poetry
check-poetry:
command -v poetry >/dev/null 2>&1 || { echo >&2 "Locust requires the poetry binary to be available in this shell to build the Python package.\nSee: https://docs.locust.io/en/stable/developing-locust.html#install-locust-for-development"; exit 1; }
Copy link
Collaborator

@cyberw cyberw Jun 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have found a nicer way to do this:
which poetry > /dev/null || {...
Or maybe that is linux/unix-specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I believe which is unix-specific - the windows equivalent would be where

Come to think of it, I think command itself is POSIX compliant and I currently have no idea if this works on Windows either 😓

Cross platform options likely boil down to:

  • write a couple more lines (have cases for each platform)
  • call out to the system's Python and have that check (we need Python anyway right)
  • maybe just call the binary and catch the errors to the pipe anyway
pyproject.toml Outdated Show resolved Hide resolved
@cyberw
Copy link
Collaborator

cyberw commented Jun 23, 2024

Looking really good now.

As for testing twine/publishing, its ok to break the publishing for a little while (maybe time the merge for when you know you have a couple hours to fix any issues that might arise?). I do have a pypy test account where we could publish, but meh.

I did have an issue with the docker image. Can you try it and see if the same thing happened to you?

> docker build -t locustio/locust:latest .
...
> docker run -p 8089:8089 -v $PWD:/mnt/locust locustio/locust -f /mnt/locust/locustfile.py
[2024-06-23 07:59:40,884] 474c796dc630/INFO/locust.main: Starting web interface at http://0.0.0.0:8089
[2024-06-23 07:59:40,890] 474c796dc630/INFO/locust.main: Starting Locust 2.29.0.dev110
(and then I opened the start page)
Traceback (most recent call last):
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 880, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 865, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/locust/web.py", line 565, in wrapper
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/locust/web.py", line 170, in index
    return render_template("index.html", template_args=self.template_args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/templating.py", line 149, in render_template
    template = app.jinja_env.get_or_select_template(template_name_or_list)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/jinja2/environment.py", line 1084, in get_or_select_template
    return self.get_template(template_name_or_list, parent, globals)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/jinja2/environment.py", line 1013, in get_template
    return self._load_template(name, globals)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/jinja2/environment.py", line 972, in _load_template
    template = self.loader.load(self, name, self.make_globals(globals))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/jinja2/loaders.py", line 126, in load
    source, filename, uptodate = self.get_source(environment, name)
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/templating.py", line 65, in get_source
    return self._get_source_fast(environment, template)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/templating.py", line 99, in _get_source_fast
    raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: index.html

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/venv/lib/python3.11/site-packages/gevent/pywsgi.py", line 1107, in handle_one_response
    self.run_application()
  File "/opt/venv/lib/python3.11/site-packages/gevent/pywsgi.py", line 1053, in run_application
    self.result = self.application(self.environ, self.start_response)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 1498, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 1476, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask_cors/extension.py", line 178, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
                                                ^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 1473, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 882, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask_cors/extension.py", line 178, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
                                                ^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/flask/app.py", line 772, in handle_user_exception
    return self.ensure_sync(handler)(e)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/locust/web.py", line 155, in handle_exception
    f"UI got request for {request.path}, but it resulted in a {error.code}: {error.name}",
                                                               ^^^^^^^^^^
AttributeError: 'TemplateNotFound' object has no attribute 'code'
2024-06-23T07:59:44Z {'REMOTE_ADDR': '::ffff:172.17.0.1', 'REMOTE_PORT': '54400', 'HTTP_HOST': '0.0.0.0:8089', (hidden keys: 25)} failed with AttributeError
@mquinnfd
Copy link
Contributor Author

mquinnfd commented Jun 23, 2024

Looking really good now.

As for testing twine/publishing, its ok to break the publishing for a little while (maybe time the merge for when you know you have a couple hours to fix any issues that might arise?). I do have a pypy test account where we could publish, but meh.

...

Yeah sounds good to me I can set aside some time for this 👍
Might end up with some good suggestions for optimising the flow etc. if we make the PR for review at some point as well

I'll give the Dockerfile another whirl now

@mquinnfd
Copy link
Contributor Author

Ok the docker image works now, thanks! I actually put the prebuilt web UI in the wrong location originally.

Updated version should work for you too. (sorry http://example.com whoever you are)

image image
@cyberw
Copy link
Collaborator

cyberw commented Jun 23, 2024

Great stuff! I’ll let @andrewbaldwin44 have a whirl when he gets back on Tuesday. Is there something left to do before you feel it is ready to merge? If not, you can take it out of draft and once Andrew has reviewed we’ll find a good time to merge.

locust/webui/.yarnrc Outdated Show resolved Hide resolved
pyproject.toml Outdated Show resolved Hide resolved
pyproject.toml Outdated Show resolved Hide resolved
@andrewbaldwin44
Copy link
Collaborator

I noticed when running the tests python -m poetry run tox -e fail_fast_test_main that it runs the build for the frontend as well. If it is only running the tests in locust/test/test_main.py, the build wouldn't be necessary. Maybe we could use SKIP_PRE_BUILD?

@andrewbaldwin44
Copy link
Collaborator

@mquinnfd I left a couple of small comments but overall this looks awesome :) Thanks so much for working on this and battling with Windows! 🥇

@mquinnfd
Copy link
Contributor Author

@mquinnfd I left a couple of small comments but overall this looks awesome :) Thanks so much for working on this and battling with Windows! 🥇

Thanks for the review man 🙇
I'll get these bits rounded up this week and then yeah might be time to open it up to the gallery, I'm sure there are some opinionated poetry bits and bobs that could be changed here

@mquinnfd
Copy link
Contributor Author

I noticed when running the tests python -m poetry run tox -e fail_fast_test_main that it runs the build for the frontend as well. If it is only running the tests in locust/test/test_main.py, the build wouldn't be necessary. Maybe we could use SKIP_PRE_BUILD?

Hey this is a great call! This shaves some time off the Windows and Linux builds 👍
Should see them skipping now - like below

Installing the current project: locust (2.29.1.dev122)
Preparing build environment with build-system requirements poetry-core>=1.0.0, poetry-dynamic-versioning>=1.0.0,<2.0.0
Skipping front end build...
fail_fast_test_main: commands[1]> poetry run python -m unittest -f locust.test.test_main

from tmp7ef0_06q_locustfile import TestUser1

.........ssssss.s...s.sss.......ss..s.....ss......sssss.
----------------------------------------------------------------------
Ran 56 tests in 135.491s

OK (skipped=21)
  fail_fast_test_main: OK (152.48=setup[1.67]+cmd[11.98,138.83] seconds)
  congratulations :) (152.62 seconds)
@mquinnfd
Copy link
Contributor Author

mquinnfd commented Jul 2, 2024

Going to open this for Public review and I believe the rest of these comments have been worked through - I'll take the opportunity while people eyeball it, to look at the publish steps next chance I get

@mquinnfd mquinnfd marked this pull request as ready for review July 2, 2024 09:25
@cyberw cyberw merged commit 0da8fa1 into locustio:master Jul 5, 2024
14 checks passed
@cyberw cyberw changed the title Poetry build system Jul 31, 2024
apereocas-bot pushed a commit to apereo/cas that referenced this pull request Aug 1, 2024
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [locust](https://locust.io/) ([source](https://togithub.com/locustio/locust)) | `==2.29.1` -> `==2.30.0` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/locust/2.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/locust/2.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/locust/2.29.1/2.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/locust/2.29.1/2.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>locustio/locust (locust)</summary>

### [`v2.30.0`](https://togithub.com/locustio/locust/releases/tag/2.30.0)

[Compare Source](https://togithub.com/locustio/locust/compare/2.29.1...2.30.0)

#### What's Changed

-   FastHttpSession: Enable passing json as a positional argument for post() and stop converting response times to int by [@&#8203;tdadela](https://togithub.com/tdadela) in [locustio/locust#2772
-   Remove Line Chart Default Zoom by [@&#8203;andrewbaldwin44](https://togithub.com/andrewbaldwin44) in [locustio/locust#2774
-   FastHttpSession requests typing  by [@&#8203;tdadela](https://togithub.com/tdadela) in [locustio/locust#2775
-   new events for heartbeat and usage monitor by [@&#8203;mgor](https://togithub.com/mgor) in [locustio/locust#2777
-   dispatch benchmark test improvements by [@&#8203;tdadela](https://togithub.com/tdadela) in [locustio/locust#2780
-   SequentialTaskSet: Allow weighted tasks and dict in .tasks by [@&#8203;bakhtos](https://togithub.com/bakhtos) in [locustio/locust#2742
-   Fix StatsEntry docstring by [@&#8203;tdadela](https://togithub.com/tdadela) in [locustio/locust#2784
-   Implement Poetry build system (mainly so we don't have to commit dynamically generated front end bundles to git) by [@&#8203;mquinnfd](https://togithub.com/mquinnfd) in [locustio/locust#2725
-   Typing: strict optional in dispatch.py by [@&#8203;tdadela](https://togithub.com/tdadela) in [locustio/locust#2779
-   Correctly set version from Poetry in published builds by [@&#8203;mquinnfd](https://togithub.com/mquinnfd) in [locustio/locust#2791
-   Fix Extend Webui Example by [@&#8203;andrewbaldwin44](https://togithub.com/andrewbaldwin44) in [locustio/locust#2800
-   Provide warning for local installs where yarn is not present by [@&#8203;mquinnfd](https://togithub.com/mquinnfd) in [locustio/locust#2801
-   Fix tests on windows by [@&#8203;mquinnfd](https://togithub.com/mquinnfd) in [locustio/locust#2803
-   Add example of a bottlenecked server and use that test to make a new graph for the docs by [@&#8203;cyberw](https://togithub.com/cyberw) in [locustio/locust#2805
-   Replace total avg response time with 50 percentile (avg was broken) by [@&#8203;andrewbaldwin44](https://togithub.com/andrewbaldwin44) in [locustio/locust#2806
-   Avoid deadlock in gevent/urllib3 connection pool (fixes occasional worker heartbeat timeouts) by [@&#8203;tdadela](https://togithub.com/tdadela) in [locustio/locust#2813
-   Fix Dockerfile style warning by [@&#8203;mehrdadbn9](https://togithub.com/mehrdadbn9) in [locustio/locust#2814
-   Fix pypy gc.freeze() AttributeError by [@&#8203;jimoleary](https://togithub.com/jimoleary) in [locustio/locust#2819
-   Update poetry windows tests by [@&#8203;mquinnfd](https://togithub.com/mquinnfd) in [locustio/locust#2821

#### New Contributors

-   [@&#8203;bakhtos](https://togithub.com/bakhtos) made their first contribution in [locustio/locust#2742
-   [@&#8203;mquinnfd](https://togithub.com/mquinnfd) made their first contribution in [locustio/locust#2725
-   [@&#8203;mehrdadbn9](https://togithub.com/mehrdadbn9) made their first contribution in [locustio/locust#2814
-   [@&#8203;jimoleary](https://togithub.com/jimoleary) made their first contribution in [locustio/locust#2819

**Full Changelog**: locustio/locust@2.29.1...2.30.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 10pm every weekday,before 6am every weekday" (UTC), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View the [repository job log](https://developer.mend.io/github/apereo/cas).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbIkJvdCIsIlJlbm92YXRlIl19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants