From bd6103c83369452dc15024471196c9b7c5c9636e Mon Sep 17 00:00:00 2001 From: Erik Aker Date: Tue, 19 Nov 2024 09:11:27 -0800 Subject: [PATCH 1/2] Further further --- .github/workflows/release.yaml | 2 +- README.md | 124 +++++++++++++++++++++++---------- benchmark/compare_werkzeug.py | 1 + justfile | 10 ++- pyproject.toml | 5 +- scripts/release.sh | 5 -- scripts/run_tests.sh | 9 --- setup.cfg | 36 ---------- uv.lock | 6 +- 9 files changed, 102 insertions(+), 96 deletions(-) delete mode 100755 scripts/release.sh delete mode 100644 scripts/run_tests.sh delete mode 100644 setup.cfg diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 67a372c..f31a3c0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,7 +23,7 @@ jobs: - name: Set up uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - name: Install pypa/build - run: uv run build + run: just build - name: Store the distribution packages uses: actions/upload-artifact@v4 with: diff --git a/README.md b/README.md index 3d36133..20aec8c 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ This example relies on the following dependencies: If we have these dependencies in our Python environment, we can run this simple script: ```sh -$ poetry run python examples/asgi_minimal.py +$ python examples/asgi_minimal.py [2022-03-20 16:59:58 -0700] [91988] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit) ``` @@ -215,36 +215,58 @@ OK **Note**: that our regex path _does not_ match capital letters, so that request 404s. -## Examples +## For Contributors -Runnable examples are provided in the [`examples` directory](https://github.com/erewok/tokamak/examples). For instance, you can run the experimental `tokamak` application with `trio` and `hypercorn` like so: +This project uses `uv` for managing dependencies and virtual environments. + +In addition, to contribute to this project, we recommend using `just`: https://github.com/casey/just + +You can run various common workflows using the above tools, try the following: ```sh -$ poetry install -E "full" -Installing dependencies from lock file - -Package operations: 8 installs, 0 updates, 0 removals - - • Installing h11 (0.13.0) - • Installing hpack (4.0.0) - • Installing hyperframe (6.0.1) - • Installing h2 (4.1.0) - • Installing priority (2.0.0) - • Installing toml (0.10.2) - • Installing wsproto (1.1.0) - • Installing hypercorn (0.13.2) - -Installing the current project: tokamak (0.2.1) -❯ poetry run python examples/tokamak_app.py +❯ just +just --list +Available recipes: + benchmark # Run the benchmark + bootstrap default="3.12" # Install dependencies used by this project + build *args # Build the project as a package (uv build) + check # Run code quality checks + check-types # Run mypy checks + ci-test coverage_dir='./coverage' # Run the project tests for CI environment (e.g. with code coverage) + example name # Run an example + format # Run the code formatter + sync # Sync dependencies with environment + test *args # Run all tests locally + +❯ just check ++ uv run ruff check tokamak tests +All checks passed! + +❯ just test ++ uv run pytest +... + +``` + +### Examples + +Runnable examples are provided in the [`examples` directory](https://github.com/erewok/tokamak/examples). In addition, this project includes a [`justfile`](./justfile) (see [just](https://github.com/casey/just)) for easily running examples. + +For instance, you can run the experimental `tokamak` application with `trio` and `hypercorn` like so: + +```sh +$ just example tokamak_app +uv run --extra examples python examples/tokamak_app.py +Installed 13 packages in 5.55s ========·°·°~> Starting tokamak °°···°°🚀···°° -[2022-03-20 11:05:01 -0700] [63023] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit) +[2024-11-19 09:01:24 -0800] [32768] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit) ``` In a separate terminal, you can make various requests, such as the following: ```sh ❯ curl http://localhost:8000 -{"received": {}} +ok ❯ curl http://localhost:8000/info/erik -d '{"some_data": "something"}' {"received": {"some_data": "something"}} @@ -253,22 +275,48 @@ In a separate terminal, you can make various requests, such as the following: Back in the first terminal, where you launched the example `tokamak` application, you should see the following: ```sh -❯ poetry run python examples/tokamak_app.py +❯ just example tokamak_app +uv run --extra examples python examples/tokamak_app.py +Installed 13 packages in 5.55s ========·°·°~> Starting tokamak °°···°°🚀···°° -[2022-03-20 11:05:01 -0700] [63023] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit) -{} {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'GET', 'scheme': 'http', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'root_path': '', 'headers': , 'client': ('127.0.0.1', 55379), 'server': ('127.0.0.1', 8000), 'extensions': {}} b'' 1.1 GET -Sleeping 1s for total seconds: 0 -Sleeping 1s for total seconds: 1 -Sleeping 1s for total seconds: 2 -Sleeping 1s for total seconds: 3 -Sleeping 1s for total seconds: 4 -{'user': 'erik'} {'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'POST', 'scheme': 'http', 'path': '/info/erik', 'raw_path': b'/info/erik', 'query_string': b'', 'root_path': '', 'headers': , 'client': ('127.0.0.1', 55386), 'server': ('127.0.0.1', 8000), 'extensions': {}} b'' 1.1 POST -Sleeping 1s for total seconds: 0 -Sleeping 1s for total seconds: 5 -Sleeping 1s for total seconds: 1 -Sleeping 1s for total seconds: 6 -Sleeping 1s for total seconds: 2 -Sleeping 1s for total seconds: 7 -Sleeping 1s for total seconds: 3 -Sleeping 1s for total seconds: 8 +[2024-11-19 09:01:24 -0800] [32768] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit) +request.app.db={}, request.context={'user': 'erik'}, request.scope={'type': 'http', 'http_version': '1.1', 'asgi': {'spec_version': '2.1', 'version': '3.0'}, 'method': 'POST', 'scheme': 'http', 'path': '/info/erik', 'raw_path': b'/info/erik', 'query_string': b'', 'root_path': '', 'headers': [(b'host', b'localhost:8000'), (b'user-agent', b'curl/8.7.1'), (b'accept', b'*/*'), (b'content-length', b'26'), (b'content-type', b'application/x-www-form-urlencoded')], 'client': ('127.0.0.1', 63965), 'server': ('127.0.0.1', 8000), 'state': {}, 'extensions': {}, 'app': }, headers=[(b'host', b'localhost:8000'), (b'user-agent', b'curl/8.7.1'), (b'accept', b'*/*'), (b'content-length', b'26'), (b'content-type', b'application/x-www-form-urlencoded')], qparams=b'', http_version='1.1', method='POST' +Sleeping 1s for total iterations: 0 +Sleeping 1s for total iterations: 1 +Sleeping 1s for total iterations: 2 ``` + +## Benchmark + +This project was iniatated around the time that the router for [`Werkzeug`](https://github.com/pallets/werkzeug.git) (which powers Flask) was rewritten as well. That router was redesigned to use a modified Radix Tree and so we created a benchmark to compare their implementation with this one. + +To run the benchmark against Werkzeug `main`, run the following: + +```sh +uv run --extra benchmarks python -m benchmark.compare_werkzeug +Path | Ratio (percent difference from baseline) +Tokamak Tree is quicker: /users/{username}/following | 0.64 +Werkzeug Tree is quicker: /repos/{owner}/{repo}/downloads | 0.80 +Werkzeug Tree is quicker: /repos/{owner}/{repo}/hooks/{id}/pings | 0.70 +... + +****** TIMING STATISTICS TOKAMAK FASTER THAN BASELINE ****** +Better Total 5270 +Best improvement (min vs baseline) 0.12283152787580384 for path / +Mean Improvement: 0.6338085629349435 +Median Improvement: 0.618251951398763 +Std Dev Improvements: 0.18516141441278625 +Mean Path Length: 19.97020872865275 +Mean Dynamic Segment Count: 0.6757115749525616 +****** TIMING STATISTICS TOKAMAK END ****** + +****** TIMING STATISTICS WERKZEUG FASTER THAN BASELINE ****** +Better Total 4730 +Best improvement (min vs baseline) 0.23255522605196324 for path /repos/{owner}/{repo}/labels/{name} +Mean Improvement: 0.6003032685655771 +Median Improvement: 0.5382439859668042 +Std Dev Improvements: 0.20348200780749615 +Mean Path Length: 36.14545454545455 +Mean Dynamic Segment Count: 2.468076109936575 +****** TIMING STATISTICS WERKZEUG END ****** +``` \ No newline at end of file diff --git a/benchmark/compare_werkzeug.py b/benchmark/compare_werkzeug.py index 5f09b48..17f2df5 100644 --- a/benchmark/compare_werkzeug.py +++ b/benchmark/compare_werkzeug.py @@ -134,6 +134,7 @@ "/repos/{owner}/{repo}/statuses/{sha}", "/repos/{owner}/{repo}/commits/{ref}/statuses", "/repos/{owner}/{repo}/commits/{ref}/status", + "/repos/{owner}/{repo}/commits/{ref}", "/search/repositories", "/search/code", "/search/issues", diff --git a/justfile b/justfile index a005f20..800fd9e 100644 --- a/justfile +++ b/justfile @@ -39,6 +39,10 @@ test *args: ci-test coverage_dir='./coverage': uv run pytest --cov=tokamak --cov-report xml --junitxml=./coverage/unittest.junit.xml -# Run the API server -examples name: - uv run --examples {{name}} \ No newline at end of file +# Run an example +example name: + uv run --extra examples python examples/{{name}}.py + +# Run the benchmark +benchmark: + uv run --extra benchmarks python -m benchmark.compare_werkzeug diff --git a/pyproject.toml b/pyproject.toml index 4453e66..42ce198 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tokamak" -version = "0.6.0" +version = "0.6.1" description = "HTTP Router based on radix trees" readme = "README.md" authors = [ @@ -54,12 +54,13 @@ docs = [ "mkdocs-material>=9.5.44", "mkdocs-section-index>=0.3.9", ] -benchmark = [ +benchmarks = [ "Werkzeug", ] examples = [ "starlette", "hypercorn", + "trio", ] [tool.uv.sources] diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 26ccfe8..0000000 --- a/scripts/release.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -e - -poetry build -python -m twine check dist/* -python -m twine upload dist/* \ No newline at end of file diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh deleted file mode 100644 index 95796f8..0000000 --- a/scripts/run_tests.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -e - -set -Eeuxo -export SOURCE_FILES="tokamak tests" - -poetry run black --check --diff $SOURCE_FILES -poetry run isort --check --diff --project=tokamak $SOURCE_FILES -# poetry run mypy $SOURCE_FILES -poetry run pytest diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 1349b45..0000000 --- a/setup.cfg +++ /dev/null @@ -1,36 +0,0 @@ -[options.entry_points] -console_scripts = - tokamak-example = scripts.tokamak_app - -[pycodestyle] -ignore = W503,E203,B305 -max-line-length = 110 - -[mypy] -disallow_untyped_defs = True -ignore_missing_imports = True - -[mypy-tests.*] -disallow_untyped_defs = False - -[tool:isort] -profile = black -combine_as_imports = True - -[tool:pytest] -addopts = - # --pycodestyle - --cov-report=html - --cov-report=term-missing:skip-covered - --cov=tokamak - --cov=tests - # -rxXs - --strict-config - --strict-markers -pythonpath = . -xfail_strict=True -filterwarnings= - # Turn warnings that aren't filtered into exceptions - error - # https://github.com/Tinche/aiofiles/issues/81 - ignore: "@coroutine" decorator is deprecated.*:DeprecationWarning diff --git a/uv.lock b/uv.lock index d2a879e..4b0ddc6 100644 --- a/uv.lock +++ b/uv.lock @@ -982,7 +982,7 @@ version = "0.6.0" source = { editable = "." } [package.optional-dependencies] -benchmark = [ +benchmarks = [ { name = "werkzeug" }, ] docs = [ @@ -999,6 +999,7 @@ docs = [ examples = [ { name = "hypercorn" }, { name = "starlette" }, + { name = "trio" }, ] web = [ { name = "trio" }, @@ -1029,8 +1030,9 @@ requires-dist = [ { name = "mkdocs-section-index", marker = "extra == 'docs'", specifier = ">=0.3.9" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.27.0" }, { name = "starlette", marker = "extra == 'examples'" }, + { name = "trio", marker = "extra == 'examples'" }, { name = "trio", marker = "extra == 'web'" }, - { name = "werkzeug", marker = "extra == 'benchmark'", git = "https://github.com/pallets/werkzeug.git?branch=main" }, + { name = "werkzeug", marker = "extra == 'benchmarks'", git = "https://github.com/pallets/werkzeug.git?branch=main" }, ] [package.metadata.requires-dev] From faa353c5d8ed716e7adc89923b819e82adc5597a Mon Sep 17 00:00:00 2001 From: Erik Aker Date: Tue, 19 Nov 2024 09:15:47 -0800 Subject: [PATCH 2/2] No artifacts --- .github/workflows/release.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f31a3c0..4c16797 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -24,10 +24,5 @@ jobs: run: curl -LsSf https://astral.sh/uv/install.sh | sh - name: Install pypa/build run: just build - - name: Store the distribution packages - uses: actions/upload-artifact@v4 - with: - name: python-package-distributions - path: dist/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1