From 9101ab470b42d7252fd6b82237483d63fa414b1c Mon Sep 17 00:00:00 2001 From: Yurii Serhiichuk Date: Mon, 30 Sep 2024 21:42:13 +0300 Subject: [PATCH 1/3] chore: V2 migrate to uv (#239) * Migrate project to rye and ruff for v2 Signed-off-by: Yurii Serhiichuk * Just run ruff Signed-off-by: Yurii Serhiichuk * Add the core package stub Signed-off-by: Yurii Serhiichuk * Move cloudevents to v1 Signed-off-by: Yurii Serhiichuk * Add extra rye configs. update locks to be OS-aware Signed-off-by: Yurii Serhiichuk * Migrate from rye to uv Signed-off-by: Yurii Serhiichuk * Use python 3.12 by default for linting Signed-off-by: Yurii Serhiichuk * Do not mention rye in docs Signed-off-by: Yurii Serhiichuk * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use stricter mypy rules. exclude v1 Signed-off-by: Yurii Serhiichuk * Run isort, flake8 Signed-off-by: Yurii Serhiichuk * fix isort Signed-off-by: Yurii Serhiichuk * Run ruff with isort Signed-off-by: Yurii Serhiichuk * Move mypy config to pyproject Signed-off-by: Yurii Serhiichuk * Exclude samples as well Signed-off-by: Yurii Serhiichuk * Exclude samples as well Signed-off-by: Yurii Serhiichuk * Fix mypy pre-commit setup Signed-off-by: Yurii Serhiichuk --------- Signed-off-by: Yurii Serhiichuk Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 42 +- .pre-commit-config.yaml | 29 +- README.md | 60 +-- RELEASING.md | 4 +- mypy.ini | 16 - pypi_packaging.py | 62 --- pyproject.toml | 159 +++++- requirements/dev.txt | 7 - requirements/publish.txt | 2 - requirements/test.txt | 13 - samples/http-image-cloudevents/client.py | 5 +- .../image_sample_server.py | 3 +- .../image_sample_test.py | 5 +- samples/http-json-cloudevents/client.py | 5 +- .../json_sample_server.py | 3 +- .../http-json-cloudevents/json_sample_test.py | 5 +- setup.py | 83 --- src/cloudevents/__init__.py | 15 + src/cloudevents/core/__init__.py | 15 + {cloudevents => src/cloudevents}/py.typed | 0 .../cloudevents/v1}/__init__.py | 0 .../cloudevents/v1}/abstract/__init__.py | 2 +- .../cloudevents/v1}/abstract/event.py | 0 .../cloudevents/v1}/conversion.py | 10 +- .../cloudevents/v1}/exceptions.py | 0 .../cloudevents/v1}/http/__init__.py | 10 +- .../cloudevents/v1}/http/conversion.py | 10 +- .../cloudevents/v1}/http/event.py | 6 +- .../cloudevents/v1}/http/event_type.py | 5 +- .../cloudevents/v1}/http/http_methods.py | 13 +- .../cloudevents/v1}/http/json_methods.py | 11 +- .../cloudevents/v1}/http/util.py | 5 +- .../cloudevents/v1}/kafka/__init__.py | 2 +- .../cloudevents/v1}/kafka/conversion.py | 10 +- .../cloudevents/v1}/kafka/exceptions.py | 2 +- src/cloudevents/v1/py.typed | 0 .../cloudevents/v1}/pydantic/__init__.py | 13 +- .../cloudevents/v1}/pydantic/fields_docs.py | 2 +- .../cloudevents/v1/pydantic/v1}/__init__.py | 4 +- .../cloudevents/v1}/pydantic/v1/conversion.py | 10 +- .../cloudevents/v1}/pydantic/v1/event.py | 14 +- .../cloudevents/v1/pydantic/v2}/__init__.py | 4 +- .../cloudevents/v1}/pydantic/v2/conversion.py | 10 +- .../cloudevents/v1}/pydantic/v2/event.py | 11 +- .../cloudevents/v1}/sdk/__init__.py | 0 .../v1}/sdk/converters/__init__.py | 6 +- .../cloudevents/v1}/sdk/converters/base.py | 2 +- .../cloudevents/v1}/sdk/converters/binary.py | 10 +- .../v1}/sdk/converters/structured.py | 8 +- .../cloudevents/v1}/sdk/converters/util.py | 0 .../cloudevents/v1}/sdk/event/__init__.py | 0 .../cloudevents/v1}/sdk/event/attribute.py | 0 .../cloudevents/v1}/sdk/event/base.py | 4 +- .../cloudevents/v1}/sdk/event/opt.py | 0 .../cloudevents/v1}/sdk/event/v03.py | 2 +- .../cloudevents/v1}/sdk/event/v1.py | 2 +- .../cloudevents/v1}/sdk/exceptions.py | 0 .../cloudevents/v1}/sdk/marshaller.py | 6 +- .../cloudevents/v1}/sdk/types.py | 0 .../cloudevents/v1}/tests/__init__.py | 0 .../cloudevents/v1}/tests/data.py | 2 +- .../v1}/tests/test_backwards_compatability.py | 29 +- .../cloudevents/v1}/tests/test_base_events.py | 5 +- .../cloudevents/v1}/tests/test_converters.py | 5 +- .../v1}/tests/test_data_encaps_refs.py | 7 +- .../v1}/tests/test_deprecated_functions.py | 3 +- .../v1}/tests/test_event_extensions.py | 3 +- .../test_event_from_request_converter.py | 9 +- .../v1}/tests/test_event_pipeline.py | 9 +- .../tests/test_event_to_request_converter.py | 7 +- .../v1}/tests/test_http_cloudevent.py | 7 +- .../v1}/tests/test_http_conversions.py | 7 +- .../cloudevents/v1}/tests/test_http_events.py | 15 +- .../v1}/tests/test_kafka_conversions.py | 11 +- .../cloudevents/v1}/tests/test_marshaller.py | 11 +- .../cloudevents/v1}/tests/test_options.py | 3 +- .../v1}/tests/test_pydantic_cloudevent.py | 11 +- .../v1}/tests/test_pydantic_conversions.py | 17 +- .../v1}/tests/test_pydantic_events.py | 19 +- .../cloudevents/v1}/tests/test_v03_event.py | 2 +- .../cloudevents/v1}/tests/test_v1_event.py | 2 +- .../cloudevents/v1}/tests/test_with_sanic.py | 7 +- tests/test_cloudevents/__init__.py | 13 + .../test_cloudevents_version.py | 19 + uv.lock | 484 ++++++++++++++++++ 85 files changed, 946 insertions(+), 488 deletions(-) delete mode 100644 mypy.ini delete mode 100644 pypi_packaging.py delete mode 100644 requirements/dev.txt delete mode 100644 requirements/publish.txt delete mode 100644 requirements/test.txt delete mode 100644 setup.py create mode 100644 src/cloudevents/__init__.py create mode 100644 src/cloudevents/core/__init__.py rename {cloudevents => src/cloudevents}/py.typed (100%) rename {cloudevents => src/cloudevents/v1}/__init__.py (100%) rename {cloudevents => src/cloudevents/v1}/abstract/__init__.py (90%) rename {cloudevents => src/cloudevents/v1}/abstract/event.py (100%) rename {cloudevents => src/cloudevents/v1}/conversion.py (97%) rename {cloudevents => src/cloudevents/v1}/exceptions.py (100%) rename {cloudevents => src/cloudevents/v1}/http/__init__.py (73%) rename {cloudevents => src/cloudevents/v1}/http/conversion.py (88%) rename {cloudevents => src/cloudevents/v1}/http/event.py (96%) rename {cloudevents => src/cloudevents/v1}/http/event_type.py (88%) rename {cloudevents => src/cloudevents/v1}/http/http_methods.py (86%) rename {cloudevents => src/cloudevents/v1}/http/json_methods.py (83%) rename {cloudevents => src/cloudevents/v1}/http/util.py (96%) rename {cloudevents => src/cloudevents/v1}/kafka/__init__.py (94%) rename {cloudevents => src/cloudevents/v1}/kafka/conversion.py (97%) rename {cloudevents => src/cloudevents/v1}/kafka/exceptions.py (92%) create mode 100644 src/cloudevents/v1/py.typed rename {cloudevents => src/cloudevents/v1}/pydantic/__init__.py (81%) rename {cloudevents => src/cloudevents/v1}/pydantic/fields_docs.py (99%) rename {cloudevents/pydantic/v2 => src/cloudevents/v1/pydantic/v1}/__init__.py (83%) rename {cloudevents => src/cloudevents/v1}/pydantic/v1/conversion.py (88%) rename {cloudevents => src/cloudevents/v1}/pydantic/v1/event.py (96%) rename {cloudevents/pydantic/v1 => src/cloudevents/v1/pydantic/v2}/__init__.py (83%) rename {cloudevents => src/cloudevents/v1}/pydantic/v2/conversion.py (88%) rename {cloudevents => src/cloudevents/v1}/pydantic/v2/event.py (96%) rename {cloudevents => src/cloudevents/v1}/sdk/__init__.py (100%) rename {cloudevents => src/cloudevents/v1}/sdk/converters/__init__.py (82%) rename {cloudevents => src/cloudevents/v1}/sdk/converters/base.py (97%) rename {cloudevents => src/cloudevents/v1}/sdk/converters/binary.py (90%) rename {cloudevents => src/cloudevents/v1}/sdk/converters/structured.py (92%) rename {cloudevents => src/cloudevents/v1}/sdk/converters/util.py (100%) rename {cloudevents => src/cloudevents/v1}/sdk/event/__init__.py (100%) rename {cloudevents => src/cloudevents/v1}/sdk/event/attribute.py (100%) rename {cloudevents => src/cloudevents/v1}/sdk/event/base.py (99%) rename {cloudevents => src/cloudevents/v1}/sdk/event/opt.py (100%) rename {cloudevents => src/cloudevents/v1}/sdk/event/v03.py (99%) rename {cloudevents => src/cloudevents/v1}/sdk/event/v1.py (98%) rename {cloudevents => src/cloudevents/v1}/sdk/exceptions.py (100%) rename {cloudevents => src/cloudevents/v1}/sdk/marshaller.py (96%) rename {cloudevents => src/cloudevents/v1}/sdk/types.py (100%) rename {cloudevents => src/cloudevents/v1}/tests/__init__.py (100%) rename {cloudevents => src/cloudevents/v1}/tests/data.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_backwards_compatability.py (62%) rename {cloudevents => src/cloudevents/v1}/tests/test_base_events.py (92%) rename {cloudevents => src/cloudevents/v1}/tests/test_converters.py (93%) rename {cloudevents => src/cloudevents/v1}/tests/test_data_encaps_refs.py (96%) rename {cloudevents => src/cloudevents/v1}/tests/test_deprecated_functions.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_event_extensions.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_event_from_request_converter.py (93%) rename {cloudevents => src/cloudevents/v1}/tests/test_event_pipeline.py (94%) rename {cloudevents => src/cloudevents/v1}/tests/test_event_to_request_converter.py (93%) rename {cloudevents => src/cloudevents/v1}/tests/test_http_cloudevent.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_http_conversions.py (96%) rename {cloudevents => src/cloudevents/v1}/tests/test_http_events.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_kafka_conversions.py (98%) rename {cloudevents => src/cloudevents/v1}/tests/test_marshaller.py (93%) rename {cloudevents => src/cloudevents/v1}/tests/test_options.py (95%) rename {cloudevents => src/cloudevents/v1}/tests/test_pydantic_cloudevent.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_pydantic_conversions.py (90%) rename {cloudevents => src/cloudevents/v1}/tests/test_pydantic_events.py (96%) rename {cloudevents => src/cloudevents/v1}/tests/test_v03_event.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_v1_event.py (97%) rename {cloudevents => src/cloudevents/v1}/tests/test_with_sanic.py (93%) create mode 100644 tests/test_cloudevents/__init__.py create mode 100644 tests/test_cloudevents/test_cloudevents_version.py create mode 100644 uv.lock diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 107bf9e7..5eb7e8e8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ name: CI -on: [push, pull_request] +on: [ push, pull_request ] jobs: @@ -8,32 +8,34 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v2 with: - python-version: '3.11' - cache: 'pip' - cache-dependency-path: 'requirements/*.txt' - - name: Install dev dependencies - run: python -m pip install -r requirements/dev.txt - - name: Run linting - run: python -m tox -e lint + enable-cache: true + cache-dependency-glob: "uv.lock" + - name: Set up Python + run: uv python install 3.12 + - name: Install the project + run: uv sync --all-extras --dev + - name: Lint + run: uv run ruff check --select I test: strategy: matrix: - python: ['3.8', '3.9', '3.10', '3.11'] - os: [ubuntu-latest, windows-latest, macos-latest] + python-version: [ '3.9', '3.10', '3.11', '3.12' ] + os: [ ubuntu-latest, windows-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v2 with: - python-version: ${{ matrix.python }} - cache: 'pip' - cache-dependency-path: 'requirements/*.txt' - - name: Install dev dependencies - run: python -m pip install -r requirements/dev.txt + enable-cache: true + cache-dependency-glob: "uv.lock" + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + - name: Install the project + run: uv sync --all-extras --dev - name: Run tests - run: python -m tox -e py # Run tox using the version of Python in `PATH` + run: uv run pytest tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75ad2ef1..72f95e9c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,27 +1,24 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-toml - - repo: https://github.com/pycqa/isort - rev: 5.13.2 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.8 hooks: - - id: isort - args: [ "--profile", "black", "--filter-files" ] - - repo: https://github.com/psf/black - rev: 24.4.2 - hooks: - - id: black - language_version: python3.11 + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.11.2 hooks: - id: mypy - files: ^(cloudevents/) - exclude: ^(cloudevents/tests/) + files: ^(src/cloudevents/|tests/) + exclude: ^(src/cloudevents/v1/) types: [ python ] - args: [ ] - additional_dependencies: - - "pydantic~=2.7" + args: [ + "--config-file=pyproject.toml", + ] diff --git a/README.md b/README.md index abcf5cbf..6682cbcc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Python SDK for [CloudEvents](https://github.com/cloudevents/spec) +# Python SDK v2 for [CloudEvents](https://github.com/cloudevents/spec) [![PyPI version](https://badge.fury.io/py/cloudevents.svg)](https://badge.fury.io/py/cloudevents) @@ -10,12 +10,11 @@ will) break with every update. This SDK current supports the following versions of CloudEvents: - v1.0 -- v0.3 ## Python SDK -Package **cloudevents** provides primitives to work with CloudEvents specification: -https://github.com/cloudevents/spec. +Package [**cloudevents**](src/cloudevents) provides primitives to work with +[CloudEvents specification](https://github.com/cloudevents/spec). ### Installing @@ -33,15 +32,15 @@ Below we will provide samples on how to send cloudevents using the popular ### Binary HTTP CloudEvent ```python -from cloudevents.http import CloudEvent -from cloudevents.conversion import to_binary +from cloudevents_v1.http import CloudEvent +from cloudevents_v1.conversion import to_binary import requests # Create a CloudEvent # - The CloudEvent "id" is generated if omitted. "specversion" defaults to "1.0". attributes = { - "type": "com.example.sampletype1", - "source": "https://example.com/event-producer", + "type": "com.example.sampletype1", + "source": "https://example.com/event-producer", } data = {"message": "Hello World!"} event = CloudEvent(attributes, data) @@ -56,15 +55,15 @@ requests.post("", data=body, headers=headers) ### Structured HTTP CloudEvent ```python -from cloudevents.conversion import to_structured -from cloudevents.http import CloudEvent +from cloudevents_v1.conversion import to_structured +from cloudevents_v1.http import CloudEvent import requests # Create a CloudEvent # - The CloudEvent "id" is generated if omitted. "specversion" defaults to "1.0". attributes = { - "type": "com.example.sampletype2", - "source": "https://example.com/event-producer", + "type": "com.example.sampletype2", + "source": "https://example.com/event-producer", } data = {"message": "Hello World!"} event = CloudEvent(attributes, data) @@ -87,7 +86,7 @@ The code below shows how to consume a cloudevent using the popular python web fr ```python from flask import Flask, request -from cloudevents.http import from_http +from cloudevents_v1.http import from_http app = Flask(__name__) @@ -95,20 +94,20 @@ app = Flask(__name__) # create an endpoint at http://localhost:/3000/ @app.route("/", methods=["POST"]) def home(): - # create a CloudEvent - event = from_http(request.headers, request.get_data()) + # create a CloudEvent + event = from_http(request.headers, request.get_data()) - # you can access cloudevent fields as seen below - print( - f"Found {event['id']} from {event['source']} with type " - f"{event['type']} and specversion {event['specversion']}" - ) + # you can access cloudevent fields as seen below + print( + f"Found {event['id']} from {event['source']} with type " + f"{event['type']} and specversion {event['specversion']}" + ) - return "", 204 + return "", 204 if __name__ == "__main__": - app.run(port=3000) + app.run(port=3000) ``` You can find a complete example of turning a CloudEvent into a HTTP request @@ -162,18 +161,13 @@ with one of the project's SDKs, please send an email to ## Maintenance -We use [black][black] and [isort][isort] for autoformatting. We set up a [tox][tox] -environment to reformat the codebase. - -e.g. - -```bash -pip install tox -tox -e reformat -``` +We use [uv][uv] for dependency and package management, [ruff][ruff] and [isort][isort] +for autoformatting and [pre-commit][pre-commit] to automate those with commit +hooks. For information on releasing version bumps see [RELEASING.md](RELEASING.md) -[black]: https://black.readthedocs.io/ +[uv]: https://docs.astral.sh/uv/ +[ruff]: https://docs.astral.sh/ruff [isort]: https://pycqa.github.io/isort/ -[tox]: https://tox.wiki/ +[pre-commit]: https://pre-commit.com diff --git a/RELEASING.md b/RELEASING.md index f6ca05b1..9240a9fe 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,7 +4,7 @@ This repository is configured to automatically publish the corresponding [PyPI package](https://pypi.org/project/cloudevents/) and GitHub Tag via GitHub Actions. To release a new CloudEvents SDK, contributors should bump `__version__` in -[cloudevents](cloudevents/__init__.py) to reflect the new release version. On merge, the action +[cloudevents](cloudevents_v1/__init__.py) to reflect the new release version. On merge, the action will automatically build and release to PyPI using [this PyPI GitHub Action](https://github.com/pypa/gh-action-pypi-publish). This action gets called on all pushes to main (such as a version branch being merged @@ -12,7 +12,7 @@ into main), but only releases a new version when the version number has changed. this action assumes pushes to main are version updates. Consequently, [pypi-release.yml](.github/workflows/pypi-release.yml) will fail if you attempt to push to main without updating `__version__` in -[cloudevents](cloudevents/__init__.py) so don't forget to do so. +[cloudevents](cloudevents_v1/__init__.py) so don't forget to do so. After a version update is merged, the script [pypi_packaging.py](pypi_packaging.py) will create a GitHub tag for the new cloudevents version using `__version__`. diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index d8fb9cc0..00000000 --- a/mypy.ini +++ /dev/null @@ -1,16 +0,0 @@ -[mypy] -plugins = pydantic.mypy -python_version = 3.8 - -pretty = True -show_error_context = True -follow_imports_for_stubs = True -# subset of mypy --strict -# https://mypy.readthedocs.io/en/stable/config_file.html -check_untyped_defs = True -disallow_incomplete_defs = True -warn_return_any = True -strict_equality = True - -[mypy-deprecation.*] -ignore_missing_imports = True diff --git a/pypi_packaging.py b/pypi_packaging.py deleted file mode 100644 index c81986d5..00000000 --- a/pypi_packaging.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2018-Present The CloudEvents Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os - -import pkg_resources - -from setup import pypi_config - - -def createTag(): - from git import Repo - - # Get local pypi cloudevents version - published_pypi_version = pkg_resources.get_distribution( - pypi_config["package_name"] - ).version - - # Ensure pypi and local package versions match - if pypi_config["version_target"] == published_pypi_version: - # Create local git tag - repo = Repo(os.getcwd()) - repo.create_tag(pypi_config["version_target"]) - - # Push git tag to remote main - origin = repo.remote() - origin.push(pypi_config["version_target"]) - - else: - # PyPI publish likely failed - print( - f"Expected {pypi_config['package_name']}=={pypi_config['version_target']} " - f"but found {pypi_config['package_name']}=={published_pypi_version}" - ) - exit(1) - - -if __name__ == "__main__": - createTag() diff --git a/pyproject.toml b/pyproject.toml index 8727d44f..52fb4bb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,142 @@ -[tool.black] +[project] +name = "cloudevents" +dynamic = ["version"] +description = "CloudEvents Python SDK" +authors = [ + { name = "The Cloud Events Contributors", email = "cncfcloudevents@gmail.com" } +] +readme = "README.md" +requires-python = ">= 3.9" +license = "Apache-2.0" +classifiers = [ + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Development Status :: 5 - Production/Stable", + "Operating System :: OS Independent", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Typing :: Typed", +] +keywords = [ + "CloudEvents", + "Eventing", + "Serverless", +] +dependencies = [ + "ruff>=0.6.8", +] + +[project.urls] +"Source code" = "https://github.com/cloudevents/sdk-python" +"Documentation" = "https://cloudevents.io" +"Home page" = "https://cloudevents.io" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +dev-dependencies = [ + "pytest>=8.3.3", + "mypy>=1.11.2", + "isort>=5.13.2", + "flake8>=7.1.1", + "pep8-naming>=0.14.1", + "flake8-print>=5.0.0", + "pre-commit>=3.8.0", + "pytest-cov>=5.0.0", +] + +[tool.uv.pip] +universal = true +generate-hashes = true + +[tool.hatch.version] +path = "src/cloudevents/__init__.py" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel.force-include] +"CHANGELOG.md" = "CHANGELOG.md" +"MAINTAINERS.md" = "MAINTAINERS.md" +"README.md" = "README.md" + +[tool.hatch.build.targets.sdist] +packages = ["src/cloudevents"] + +[tool.hatch.build.targets.sdist.force-include] +"CHANGELOG.md" = "CHANGELOG.md" +"MAINTAINERS.md" = "MAINTAINERS.md" + +[tool.ruff] line-length = 88 -include = '\.pyi?$' -exclude = ''' -/( - \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist -)/ -''' - -[tool.isort] -profile = "black" +target-version = "py38" + +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "site-packages", + "venv", +] + +[tool.ruff.lint] +ignore = ["E731"] +extend-ignore = ["E203"] + +[tool.pytest.ini_options] +testpaths = [ + "tests", +] + +[tool.mypy] +python_version = 3.9 + +ignore_missing_imports = true +namespace_packages = true +explicit_package_bases = true +scripts_are_modules = true +pretty = true +show_error_context = true +follow_imports_for_stubs = true +warn_redundant_casts = true +warn_unused_ignores = true +# subset of mypy --strict +# https://mypy.readthedocs.io/en/stable/config_file.html +check_untyped_defs = true +disallow_incomplete_defs = true +warn_return_any = true +strict_equality = true +disallow_untyped_defs = true +exclude = [ + "src/cloudevents/v1", +] diff --git a/requirements/dev.txt b/requirements/dev.txt deleted file mode 100644 index 63872949..00000000 --- a/requirements/dev.txt +++ /dev/null @@ -1,7 +0,0 @@ -black -isort -flake8 -pep8-naming -flake8-print -tox -pre-commit diff --git a/requirements/publish.txt b/requirements/publish.txt deleted file mode 100644 index a296666f..00000000 --- a/requirements/publish.txt +++ /dev/null @@ -1,2 +0,0 @@ -GitPython -cloudevents diff --git a/requirements/test.txt b/requirements/test.txt deleted file mode 100644 index 3e32e4a8..00000000 --- a/requirements/test.txt +++ /dev/null @@ -1,13 +0,0 @@ -flake8 -pep8-naming -flake8-print -pytest -pytest-cov -# web app tests -sanic -sanic-testing -aiohttp -Pillow -requests -flask -pydantic>=2.0.0,<3.0 diff --git a/samples/http-image-cloudevents/client.py b/samples/http-image-cloudevents/client.py index 021c1f56..a61303f1 100644 --- a/samples/http-image-cloudevents/client.py +++ b/samples/http-image-cloudevents/client.py @@ -15,9 +15,8 @@ import sys import requests - -from cloudevents.conversion import to_binary, to_structured -from cloudevents.http import CloudEvent +from cloudevents_v1.conversion import to_binary, to_structured +from cloudevents_v1.http import CloudEvent resp = requests.get( "https://raw.githubusercontent.com/cncf/artwork/master/projects/cloudevents/horizontal/color/cloudevents-horizontal-color.png" # noqa diff --git a/samples/http-image-cloudevents/image_sample_server.py b/samples/http-image-cloudevents/image_sample_server.py index da303025..ead3e596 100644 --- a/samples/http-image-cloudevents/image_sample_server.py +++ b/samples/http-image-cloudevents/image_sample_server.py @@ -14,11 +14,10 @@ import io +from cloudevents_v1.http import from_http from flask import Flask, request from PIL import Image -from cloudevents.http import from_http - app = Flask(__name__) diff --git a/samples/http-image-cloudevents/image_sample_test.py b/samples/http-image-cloudevents/image_sample_test.py index 5fe6ec9d..33895c69 100644 --- a/samples/http-image-cloudevents/image_sample_test.py +++ b/samples/http-image-cloudevents/image_sample_test.py @@ -18,12 +18,11 @@ import pytest from client import image_bytes +from cloudevents_v1.conversion import to_binary, to_structured +from cloudevents_v1.http import CloudEvent, from_http from image_sample_server import app from PIL import Image -from cloudevents.conversion import to_binary, to_structured -from cloudevents.http import CloudEvent, from_http - image_fileobj = io.BytesIO(image_bytes) image_expected_shape = (1880, 363) diff --git a/samples/http-json-cloudevents/client.py b/samples/http-json-cloudevents/client.py index 5ecc3793..f68f27b3 100644 --- a/samples/http-json-cloudevents/client.py +++ b/samples/http-json-cloudevents/client.py @@ -15,9 +15,8 @@ import sys import requests - -from cloudevents.conversion import to_binary, to_structured -from cloudevents.http import CloudEvent +from cloudevents_v1.conversion import to_binary, to_structured +from cloudevents_v1.http import CloudEvent def send_binary_cloud_event(url): diff --git a/samples/http-json-cloudevents/json_sample_server.py b/samples/http-json-cloudevents/json_sample_server.py index c3a399ee..9fa1a6d1 100644 --- a/samples/http-json-cloudevents/json_sample_server.py +++ b/samples/http-json-cloudevents/json_sample_server.py @@ -12,10 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +from cloudevents_v1.http import from_http from flask import Flask, request -from cloudevents.http import from_http - app = Flask(__name__) diff --git a/samples/http-json-cloudevents/json_sample_test.py b/samples/http-json-cloudevents/json_sample_test.py index 1d92874d..612aade0 100644 --- a/samples/http-json-cloudevents/json_sample_test.py +++ b/samples/http-json-cloudevents/json_sample_test.py @@ -13,11 +13,10 @@ # under the License. import pytest +from cloudevents_v1.conversion import to_binary, to_structured +from cloudevents_v1.http import CloudEvent from json_sample_server import app -from cloudevents.conversion import to_binary, to_structured -from cloudevents.http import CloudEvent - @pytest.fixture def client(): diff --git a/setup.py b/setup.py deleted file mode 100644 index a4e4befc..00000000 --- a/setup.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2018-Present The CloudEvents Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import codecs -import os -import pathlib - -from setuptools import find_packages, setup - - -def read(rel_path): - here = os.path.abspath(os.path.dirname(__file__)) - with codecs.open(os.path.join(here, rel_path), "r") as fp: - return fp.read() - - -def get_version(rel_path): - for line in read(rel_path).splitlines(): - if line.startswith("__version__"): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - else: - raise RuntimeError("Unable to find version string.") - - -# FORMAT: 1.x.x -pypi_config = { - "version_target": get_version("cloudevents/__init__.py"), - "package_name": "cloudevents", -} - -here = pathlib.Path(__file__).parent.resolve() -long_description = (here / "README.md").read_text(encoding="utf-8") - -if __name__ == "__main__": - setup( - name=pypi_config["package_name"], - summary="CloudEvents Python SDK", - long_description_content_type="text/markdown", - long_description=long_description, - description="CloudEvents Python SDK", - url="https://github.com/cloudevents/sdk-python", - author="The Cloud Events Contributors", - author_email="cncfcloudevents@gmail.com", - home_page="https://cloudevents.io", - classifiers=[ - "Intended Audience :: Information Technology", - "Intended Audience :: System Administrators", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Development Status :: 5 - Production/Stable", - "Operating System :: OS Independent", - "Natural Language :: English", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Typing :: Typed", - ], - keywords="CloudEvents Eventing Serverless", - license="https://www.apache.org/licenses/LICENSE-2.0", - license_file="LICENSE", - packages=find_packages(exclude=["cloudevents.tests"]), - include_package_data=True, - version=pypi_config["version_target"], - install_requires=["deprecation>=2.0,<3.0"], - extras_require={"pydantic": "pydantic>=1.0.0,<3.0"}, - zip_safe=True, - ) diff --git a/src/cloudevents/__init__.py b/src/cloudevents/__init__.py new file mode 100644 index 00000000..9ef80432 --- /dev/null +++ b/src/cloudevents/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +__version__ = "2.0.0-alpha1" diff --git a/src/cloudevents/core/__init__.py b/src/cloudevents/core/__init__.py new file mode 100644 index 00000000..e01d2a11 --- /dev/null +++ b/src/cloudevents/core/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""This package contains the core functionality of the CloudEvents spec.""" diff --git a/cloudevents/py.typed b/src/cloudevents/py.typed similarity index 100% rename from cloudevents/py.typed rename to src/cloudevents/py.typed diff --git a/cloudevents/__init__.py b/src/cloudevents/v1/__init__.py similarity index 100% rename from cloudevents/__init__.py rename to src/cloudevents/v1/__init__.py diff --git a/cloudevents/abstract/__init__.py b/src/cloudevents/v1/abstract/__init__.py similarity index 90% rename from cloudevents/abstract/__init__.py rename to src/cloudevents/v1/abstract/__init__.py index 4000c8a7..13ebec5b 100644 --- a/cloudevents/abstract/__init__.py +++ b/src/cloudevents/v1/abstract/__init__.py @@ -12,6 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.abstract.event import AnyCloudEvent, CloudEvent +from cloudevents_v1.abstract.event import AnyCloudEvent, CloudEvent __all__ = ["AnyCloudEvent", "CloudEvent"] diff --git a/cloudevents/abstract/event.py b/src/cloudevents/v1/abstract/event.py similarity index 100% rename from cloudevents/abstract/event.py rename to src/cloudevents/v1/abstract/event.py diff --git a/cloudevents/conversion.py b/src/cloudevents/v1/conversion.py similarity index 97% rename from cloudevents/conversion.py rename to src/cloudevents/v1/conversion.py index c73e3ed0..3d9899a2 100644 --- a/cloudevents/conversion.py +++ b/src/cloudevents/v1/conversion.py @@ -16,11 +16,11 @@ import json import typing -from cloudevents import exceptions as cloud_exceptions -from cloudevents.abstract import AnyCloudEvent -from cloudevents.sdk import converters, marshaller, types -from cloudevents.sdk.converters import is_binary -from cloudevents.sdk.event import v1, v03 +from cloudevents_v1 import exceptions as cloud_exceptions +from cloudevents_v1.abstract import AnyCloudEvent +from cloudevents_v1.sdk import converters, marshaller, types +from cloudevents_v1.sdk.converters import is_binary +from cloudevents_v1.sdk.event import v03, v1 def _best_effort_serialize_to_json( # type: ignore[no-untyped-def] diff --git a/cloudevents/exceptions.py b/src/cloudevents/v1/exceptions.py similarity index 100% rename from cloudevents/exceptions.py rename to src/cloudevents/v1/exceptions.py diff --git a/cloudevents/http/__init__.py b/src/cloudevents/v1/http/__init__.py similarity index 73% rename from cloudevents/http/__init__.py rename to src/cloudevents/v1/http/__init__.py index 6e75636e..86f6030a 100644 --- a/cloudevents/http/__init__.py +++ b/src/cloudevents/v1/http/__init__.py @@ -13,16 +13,16 @@ # under the License. -from cloudevents.http.conversion import from_dict, from_http, from_json -from cloudevents.http.event import CloudEvent -from cloudevents.http.event_type import is_binary, is_structured # deprecated -from cloudevents.http.http_methods import ( # deprecated +from cloudevents_v1.http.conversion import from_dict, from_http, from_json +from cloudevents_v1.http.event import CloudEvent +from cloudevents_v1.http.event_type import is_binary, is_structured # deprecated +from cloudevents_v1.http.http_methods import ( # deprecated to_binary, to_binary_http, to_structured, to_structured_http, ) -from cloudevents.http.json_methods import to_json # deprecated +from cloudevents_v1.http.json_methods import to_json # deprecated __all__ = [ "to_binary", diff --git a/cloudevents/http/conversion.py b/src/cloudevents/v1/http/conversion.py similarity index 88% rename from cloudevents/http/conversion.py rename to src/cloudevents/v1/http/conversion.py index a7da926b..050eb25c 100644 --- a/cloudevents/http/conversion.py +++ b/src/cloudevents/v1/http/conversion.py @@ -14,11 +14,11 @@ import typing -from cloudevents.conversion import from_dict as _abstract_from_dict -from cloudevents.conversion import from_http as _abstract_from_http -from cloudevents.conversion import from_json as _abstract_from_json -from cloudevents.http.event import CloudEvent -from cloudevents.sdk import types +from cloudevents_v1.conversion import from_dict as _abstract_from_dict +from cloudevents_v1.conversion import from_http as _abstract_from_http +from cloudevents_v1.conversion import from_json as _abstract_from_json +from cloudevents_v1.http.event import CloudEvent +from cloudevents_v1.sdk import types def from_json( diff --git a/cloudevents/http/event.py b/src/cloudevents/v1/http/event.py similarity index 96% rename from cloudevents/http/event.py rename to src/cloudevents/v1/http/event.py index c7a066d6..69f38110 100644 --- a/cloudevents/http/event.py +++ b/src/cloudevents/v1/http/event.py @@ -16,9 +16,9 @@ import typing import uuid -import cloudevents.exceptions as cloud_exceptions -from cloudevents import abstract -from cloudevents.sdk.event import v1, v03 +import cloudevents_v1.exceptions as cloud_exceptions +from cloudevents_v1 import abstract +from cloudevents_v1.sdk.event import v03, v1 _required_by_version = { "1.0": v1.Event._ce_required_fields, diff --git a/cloudevents/http/event_type.py b/src/cloudevents/v1/http/event_type.py similarity index 88% rename from cloudevents/http/event_type.py rename to src/cloudevents/v1/http/event_type.py index 52259e1e..38fceffb 100644 --- a/cloudevents/http/event_type.py +++ b/src/cloudevents/v1/http/event_type.py @@ -13,11 +13,10 @@ # under the License. import typing +from cloudevents_v1.sdk.converters import is_binary as _moved_is_binary +from cloudevents_v1.sdk.converters import is_structured as _moved_is_structured from deprecation import deprecated -from cloudevents.sdk.converters import is_binary as _moved_is_binary -from cloudevents.sdk.converters import is_structured as _moved_is_structured - # THIS MODULE IS DEPRECATED, YOU SHOULD NOT ADD NEW FUNCTIONALLY HERE diff --git a/cloudevents/http/http_methods.py b/src/cloudevents/v1/http/http_methods.py similarity index 86% rename from cloudevents/http/http_methods.py rename to src/cloudevents/v1/http/http_methods.py index 091c51b5..fe5cd42b 100644 --- a/cloudevents/http/http_methods.py +++ b/src/cloudevents/v1/http/http_methods.py @@ -14,15 +14,14 @@ import typing +from cloudevents_v1.abstract import AnyCloudEvent +from cloudevents_v1.conversion import to_binary as _moved_to_binary +from cloudevents_v1.conversion import to_structured as _moved_to_structured +from cloudevents_v1.http.conversion import from_http as _moved_from_http +from cloudevents_v1.http.event import CloudEvent +from cloudevents_v1.sdk import types from deprecation import deprecated -from cloudevents.abstract import AnyCloudEvent -from cloudevents.conversion import to_binary as _moved_to_binary -from cloudevents.conversion import to_structured as _moved_to_structured -from cloudevents.http.conversion import from_http as _moved_from_http -from cloudevents.http.event import CloudEvent -from cloudevents.sdk import types - # THIS MODULE IS DEPRECATED, YOU SHOULD NOT ADD NEW FUNCTIONALLY HERE diff --git a/cloudevents/http/json_methods.py b/src/cloudevents/v1/http/json_methods.py similarity index 83% rename from cloudevents/http/json_methods.py rename to src/cloudevents/v1/http/json_methods.py index 58e322c7..28a9873f 100644 --- a/cloudevents/http/json_methods.py +++ b/src/cloudevents/v1/http/json_methods.py @@ -14,14 +14,13 @@ import typing +from cloudevents_v1.abstract import AnyCloudEvent +from cloudevents_v1.conversion import to_json as _moved_to_json +from cloudevents_v1.http import CloudEvent +from cloudevents_v1.http.conversion import from_json as _moved_from_json +from cloudevents_v1.sdk import types from deprecation import deprecated -from cloudevents.abstract import AnyCloudEvent -from cloudevents.conversion import to_json as _moved_to_json -from cloudevents.http import CloudEvent -from cloudevents.http.conversion import from_json as _moved_from_json -from cloudevents.sdk import types - # THIS MODULE IS DEPRECATED, YOU SHOULD NOT ADD NEW FUNCTIONALLY HERE diff --git a/cloudevents/http/util.py b/src/cloudevents/v1/http/util.py similarity index 96% rename from cloudevents/http/util.py rename to src/cloudevents/v1/http/util.py index f44395e6..8158fb66 100644 --- a/cloudevents/http/util.py +++ b/src/cloudevents/v1/http/util.py @@ -13,11 +13,10 @@ # under the License. import typing -from deprecation import deprecated - -from cloudevents.conversion import ( +from cloudevents_v1.conversion import ( _best_effort_serialize_to_json as _moved_default_marshaller, ) +from deprecation import deprecated # THIS MODULE IS DEPRECATED, YOU SHOULD NOT ADD NEW FUNCTIONALLY HERE diff --git a/cloudevents/kafka/__init__.py b/src/cloudevents/v1/kafka/__init__.py similarity index 94% rename from cloudevents/kafka/__init__.py rename to src/cloudevents/v1/kafka/__init__.py index fbe1dfb0..b5648c30 100644 --- a/cloudevents/kafka/__init__.py +++ b/src/cloudevents/v1/kafka/__init__.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.kafka.conversion import ( +from cloudevents_v1.kafka.conversion import ( KafkaMessage, KeyMapper, from_binary, diff --git a/cloudevents/kafka/conversion.py b/src/cloudevents/v1/kafka/conversion.py similarity index 97% rename from cloudevents/kafka/conversion.py rename to src/cloudevents/v1/kafka/conversion.py index 97c355f2..e7f30733 100644 --- a/cloudevents/kafka/conversion.py +++ b/src/cloudevents/v1/kafka/conversion.py @@ -15,11 +15,11 @@ import json import typing -from cloudevents import exceptions as cloud_exceptions -from cloudevents import http -from cloudevents.abstract import AnyCloudEvent -from cloudevents.kafka.exceptions import KeyMapperError -from cloudevents.sdk import types +from cloudevents_v1 import exceptions as cloud_exceptions +from cloudevents_v1 import http +from cloudevents_v1.abstract import AnyCloudEvent +from cloudevents_v1.kafka.exceptions import KeyMapperError +from cloudevents_v1.sdk import types DEFAULT_MARSHALLER: types.MarshallerType = json.dumps DEFAULT_UNMARSHALLER: types.MarshallerType = json.loads diff --git a/cloudevents/kafka/exceptions.py b/src/cloudevents/v1/kafka/exceptions.py similarity index 92% rename from cloudevents/kafka/exceptions.py rename to src/cloudevents/v1/kafka/exceptions.py index 6459f0a2..352e9d24 100644 --- a/cloudevents/kafka/exceptions.py +++ b/src/cloudevents/v1/kafka/exceptions.py @@ -11,7 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from cloudevents import exceptions as cloud_exceptions +from cloudevents_v1 import exceptions as cloud_exceptions class KeyMapperError(cloud_exceptions.GenericException): diff --git a/src/cloudevents/v1/py.typed b/src/cloudevents/v1/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/cloudevents/pydantic/__init__.py b/src/cloudevents/v1/pydantic/__init__.py similarity index 81% rename from cloudevents/pydantic/__init__.py rename to src/cloudevents/v1/pydantic/__init__.py index f8556ca1..4d22fe41 100644 --- a/cloudevents/pydantic/__init__.py +++ b/src/cloudevents/v1/pydantic/__init__.py @@ -14,24 +14,29 @@ from typing import TYPE_CHECKING -from cloudevents.exceptions import PydanticFeatureNotInstalled +from cloudevents_v1.exceptions import PydanticFeatureNotInstalled try: if TYPE_CHECKING: - from cloudevents.pydantic.v2 import CloudEvent, from_dict, from_http, from_json + from cloudevents_v1.pydantic.v2 import ( + CloudEvent, + from_dict, + from_http, + from_json, + ) else: from pydantic import VERSION as PYDANTIC_VERSION pydantic_major_version = PYDANTIC_VERSION.split(".")[0] if pydantic_major_version == "1": - from cloudevents.pydantic.v1 import ( + from cloudevents_v1.pydantic.v1 import ( CloudEvent, from_dict, from_http, from_json, ) else: - from cloudevents.pydantic.v2 import ( + from cloudevents_v1.pydantic.v2 import ( CloudEvent, from_dict, from_http, diff --git a/cloudevents/pydantic/fields_docs.py b/src/cloudevents/v1/pydantic/fields_docs.py similarity index 99% rename from cloudevents/pydantic/fields_docs.py rename to src/cloudevents/v1/pydantic/fields_docs.py index 00ed0bd3..0abeaf15 100644 --- a/cloudevents/pydantic/fields_docs.py +++ b/src/cloudevents/v1/pydantic/fields_docs.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.sdk.event import attribute +from cloudevents_v1.sdk.event import attribute FIELD_DESCRIPTIONS = { "data": { diff --git a/cloudevents/pydantic/v2/__init__.py b/src/cloudevents/v1/pydantic/v1/__init__.py similarity index 83% rename from cloudevents/pydantic/v2/__init__.py rename to src/cloudevents/v1/pydantic/v1/__init__.py index 55d2a7fd..3b0e435c 100644 --- a/cloudevents/pydantic/v2/__init__.py +++ b/src/cloudevents/v1/pydantic/v1/__init__.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.pydantic.v2.conversion import from_dict, from_http, from_json -from cloudevents.pydantic.v2.event import CloudEvent +from cloudevents_v1.pydantic.v1.conversion import from_dict, from_http, from_json +from cloudevents_v1.pydantic.v1.event import CloudEvent __all__ = ["CloudEvent", "from_json", "from_dict", "from_http"] diff --git a/cloudevents/pydantic/v1/conversion.py b/src/cloudevents/v1/pydantic/v1/conversion.py similarity index 88% rename from cloudevents/pydantic/v1/conversion.py rename to src/cloudevents/v1/pydantic/v1/conversion.py index dcf0b7db..efd7a7f4 100644 --- a/cloudevents/pydantic/v1/conversion.py +++ b/src/cloudevents/v1/pydantic/v1/conversion.py @@ -13,11 +13,11 @@ # under the License. import typing -from cloudevents.conversion import from_dict as _abstract_from_dict -from cloudevents.conversion import from_http as _abstract_from_http -from cloudevents.conversion import from_json as _abstract_from_json -from cloudevents.pydantic.v1.event import CloudEvent -from cloudevents.sdk import types +from cloudevents_v1.conversion import from_dict as _abstract_from_dict +from cloudevents_v1.conversion import from_http as _abstract_from_http +from cloudevents_v1.conversion import from_json as _abstract_from_json +from cloudevents_v1.pydantic.v1.event import CloudEvent +from cloudevents_v1.sdk import types def from_http( diff --git a/cloudevents/pydantic/v1/event.py b/src/cloudevents/v1/pydantic/v1/event.py similarity index 96% rename from cloudevents/pydantic/v1/event.py rename to src/cloudevents/v1/pydantic/v1/event.py index d18736a4..999828b3 100644 --- a/cloudevents/pydantic/v1/event.py +++ b/src/cloudevents/v1/pydantic/v1/event.py @@ -15,8 +15,8 @@ import json import typing -from cloudevents.exceptions import PydanticFeatureNotInstalled -from cloudevents.pydantic.fields_docs import FIELD_DESCRIPTIONS +from cloudevents_v1.exceptions import PydanticFeatureNotInstalled +from cloudevents_v1.pydantic.fields_docs import FIELD_DESCRIPTIONS try: from pydantic import VERSION as PYDANTIC_VERSION @@ -32,9 +32,9 @@ "Install it using pip install cloudevents[pydantic]" ) -from cloudevents import abstract, conversion, http -from cloudevents.exceptions import IncompatibleArgumentsError -from cloudevents.sdk.event import attribute +from cloudevents_v1 import abstract, conversion, http +from cloudevents_v1.exceptions import IncompatibleArgumentsError +from cloudevents_v1.sdk.event import attribute def _ce_json_dumps( # type: ignore[no-untyped-def] @@ -71,7 +71,9 @@ def _ce_json_dumps( # type: ignore[no-untyped-def] def _ce_json_loads( # type: ignore[no-untyped-def] - data: typing.AnyStr, *args, **kwargs # noqa + data: typing.AnyStr, + *args, + **kwargs, # noqa ) -> typing.Dict[typing.Any, typing.Any]: """Performs Pydantic-specific deserialization of the event. diff --git a/cloudevents/pydantic/v1/__init__.py b/src/cloudevents/v1/pydantic/v2/__init__.py similarity index 83% rename from cloudevents/pydantic/v1/__init__.py rename to src/cloudevents/v1/pydantic/v2/__init__.py index e17151a4..0bda7d88 100644 --- a/cloudevents/pydantic/v1/__init__.py +++ b/src/cloudevents/v1/pydantic/v2/__init__.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.pydantic.v1.conversion import from_dict, from_http, from_json -from cloudevents.pydantic.v1.event import CloudEvent +from cloudevents_v1.pydantic.v2.conversion import from_dict, from_http, from_json +from cloudevents_v1.pydantic.v2.event import CloudEvent __all__ = ["CloudEvent", "from_json", "from_dict", "from_http"] diff --git a/cloudevents/pydantic/v2/conversion.py b/src/cloudevents/v1/pydantic/v2/conversion.py similarity index 88% rename from cloudevents/pydantic/v2/conversion.py rename to src/cloudevents/v1/pydantic/v2/conversion.py index 65108544..a164091b 100644 --- a/cloudevents/pydantic/v2/conversion.py +++ b/src/cloudevents/v1/pydantic/v2/conversion.py @@ -14,11 +14,11 @@ import typing -from cloudevents.conversion import from_dict as _abstract_from_dict -from cloudevents.conversion import from_http as _abstract_from_http -from cloudevents.conversion import from_json as _abstract_from_json -from cloudevents.pydantic.v2.event import CloudEvent -from cloudevents.sdk import types +from cloudevents_v1.conversion import from_dict as _abstract_from_dict +from cloudevents_v1.conversion import from_http as _abstract_from_http +from cloudevents_v1.conversion import from_json as _abstract_from_json +from cloudevents_v1.pydantic.v2.event import CloudEvent +from cloudevents_v1.sdk import types def from_http( diff --git a/cloudevents/pydantic/v2/event.py b/src/cloudevents/v1/pydantic/v2/event.py similarity index 96% rename from cloudevents/pydantic/v2/event.py rename to src/cloudevents/v1/pydantic/v2/event.py index 643794c1..26c2fcb9 100644 --- a/cloudevents/pydantic/v2/event.py +++ b/src/cloudevents/v1/pydantic/v2/event.py @@ -17,11 +17,10 @@ import typing from typing import Any +from cloudevents_v1.exceptions import PydanticFeatureNotInstalled +from cloudevents_v1.pydantic.fields_docs import FIELD_DESCRIPTIONS from pydantic.deprecated import parse as _deprecated_parse -from cloudevents.exceptions import PydanticFeatureNotInstalled -from cloudevents.pydantic.fields_docs import FIELD_DESCRIPTIONS - try: from pydantic import BaseModel, ConfigDict, Field, model_serializer except ImportError: # pragma: no cover # hard to test @@ -30,9 +29,9 @@ "Install it using pip install cloudevents[pydantic]" ) -from cloudevents import abstract, conversion -from cloudevents.exceptions import IncompatibleArgumentsError -from cloudevents.sdk.event import attribute +from cloudevents_v1 import abstract, conversion +from cloudevents_v1.exceptions import IncompatibleArgumentsError +from cloudevents_v1.sdk.event import attribute class CloudEvent(abstract.CloudEvent, BaseModel): # type: ignore diff --git a/cloudevents/sdk/__init__.py b/src/cloudevents/v1/sdk/__init__.py similarity index 100% rename from cloudevents/sdk/__init__.py rename to src/cloudevents/v1/sdk/__init__.py diff --git a/cloudevents/sdk/converters/__init__.py b/src/cloudevents/v1/sdk/converters/__init__.py similarity index 82% rename from cloudevents/sdk/converters/__init__.py rename to src/cloudevents/v1/sdk/converters/__init__.py index cd8df680..c70f1464 100644 --- a/cloudevents/sdk/converters/__init__.py +++ b/src/cloudevents/v1/sdk/converters/__init__.py @@ -12,9 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.sdk.converters import binary, structured -from cloudevents.sdk.converters.binary import is_binary -from cloudevents.sdk.converters.structured import is_structured +from cloudevents_v1.sdk.converters import binary, structured +from cloudevents_v1.sdk.converters.binary import is_binary +from cloudevents_v1.sdk.converters.structured import is_structured TypeBinary: str = binary.BinaryHTTPCloudEventConverter.TYPE TypeStructured: str = structured.JSONHTTPCloudEventConverter.TYPE diff --git a/cloudevents/sdk/converters/base.py b/src/cloudevents/v1/sdk/converters/base.py similarity index 97% rename from cloudevents/sdk/converters/base.py rename to src/cloudevents/v1/sdk/converters/base.py index 43edf5d2..c0b0b3fb 100644 --- a/cloudevents/sdk/converters/base.py +++ b/src/cloudevents/v1/sdk/converters/base.py @@ -14,7 +14,7 @@ import typing -from cloudevents.sdk.event import base +from cloudevents_v1.sdk.event import base class Converter(object): diff --git a/cloudevents/sdk/converters/binary.py b/src/cloudevents/v1/sdk/converters/binary.py similarity index 90% rename from cloudevents/sdk/converters/binary.py rename to src/cloudevents/v1/sdk/converters/binary.py index c5fcbf54..a06d92ce 100644 --- a/cloudevents/sdk/converters/binary.py +++ b/src/cloudevents/v1/sdk/converters/binary.py @@ -14,11 +14,11 @@ import typing -from cloudevents.sdk import exceptions, types -from cloudevents.sdk.converters import base -from cloudevents.sdk.converters.util import has_binary_headers -from cloudevents.sdk.event import base as event_base -from cloudevents.sdk.event import v1, v03 +from cloudevents_v1.sdk import exceptions, types +from cloudevents_v1.sdk.converters import base +from cloudevents_v1.sdk.converters.util import has_binary_headers +from cloudevents_v1.sdk.event import base as event_base +from cloudevents_v1.sdk.event import v03, v1 class BinaryHTTPCloudEventConverter(base.Converter): diff --git a/cloudevents/sdk/converters/structured.py b/src/cloudevents/v1/sdk/converters/structured.py similarity index 92% rename from cloudevents/sdk/converters/structured.py rename to src/cloudevents/v1/sdk/converters/structured.py index 24eda895..b5e090ef 100644 --- a/cloudevents/sdk/converters/structured.py +++ b/src/cloudevents/v1/sdk/converters/structured.py @@ -14,10 +14,10 @@ import typing -from cloudevents.sdk import types -from cloudevents.sdk.converters import base -from cloudevents.sdk.converters.util import has_binary_headers -from cloudevents.sdk.event import base as event_base +from cloudevents_v1.sdk import types +from cloudevents_v1.sdk.converters import base +from cloudevents_v1.sdk.converters.util import has_binary_headers +from cloudevents_v1.sdk.event import base as event_base # TODO: Singleton? diff --git a/cloudevents/sdk/converters/util.py b/src/cloudevents/v1/sdk/converters/util.py similarity index 100% rename from cloudevents/sdk/converters/util.py rename to src/cloudevents/v1/sdk/converters/util.py diff --git a/cloudevents/sdk/event/__init__.py b/src/cloudevents/v1/sdk/event/__init__.py similarity index 100% rename from cloudevents/sdk/event/__init__.py rename to src/cloudevents/v1/sdk/event/__init__.py diff --git a/cloudevents/sdk/event/attribute.py b/src/cloudevents/v1/sdk/event/attribute.py similarity index 100% rename from cloudevents/sdk/event/attribute.py rename to src/cloudevents/v1/sdk/event/attribute.py diff --git a/cloudevents/sdk/event/base.py b/src/cloudevents/v1/sdk/event/base.py similarity index 99% rename from cloudevents/sdk/event/base.py rename to src/cloudevents/v1/sdk/event/base.py index 53e05d35..f207e7cb 100644 --- a/cloudevents/sdk/event/base.py +++ b/src/cloudevents/v1/sdk/event/base.py @@ -17,8 +17,8 @@ import typing from typing import Set -import cloudevents.exceptions as cloud_exceptions -from cloudevents.sdk import types +import cloudevents_v1.exceptions as cloud_exceptions +from cloudevents_v1.sdk import types # TODO(slinkydeveloper) is this really needed? diff --git a/cloudevents/sdk/event/opt.py b/src/cloudevents/v1/sdk/event/opt.py similarity index 100% rename from cloudevents/sdk/event/opt.py rename to src/cloudevents/v1/sdk/event/opt.py diff --git a/cloudevents/sdk/event/v03.py b/src/cloudevents/v1/sdk/event/v03.py similarity index 99% rename from cloudevents/sdk/event/v03.py rename to src/cloudevents/v1/sdk/event/v03.py index 6d69d2ab..d686b536 100644 --- a/cloudevents/sdk/event/v03.py +++ b/src/cloudevents/v1/sdk/event/v03.py @@ -13,7 +13,7 @@ # under the License. import typing -from cloudevents.sdk.event import base, opt +from cloudevents_v1.sdk.event import base, opt class Event(base.BaseEvent): diff --git a/cloudevents/sdk/event/v1.py b/src/cloudevents/v1/sdk/event/v1.py similarity index 98% rename from cloudevents/sdk/event/v1.py rename to src/cloudevents/v1/sdk/event/v1.py index 18d1f3af..dfa470d1 100644 --- a/cloudevents/sdk/event/v1.py +++ b/src/cloudevents/v1/sdk/event/v1.py @@ -13,7 +13,7 @@ # under the License. import typing -from cloudevents.sdk.event import base, opt +from cloudevents_v1.sdk.event import base, opt class Event(base.BaseEvent): diff --git a/cloudevents/sdk/exceptions.py b/src/cloudevents/v1/sdk/exceptions.py similarity index 100% rename from cloudevents/sdk/exceptions.py rename to src/cloudevents/v1/sdk/exceptions.py diff --git a/cloudevents/sdk/marshaller.py b/src/cloudevents/v1/sdk/marshaller.py similarity index 96% rename from cloudevents/sdk/marshaller.py rename to src/cloudevents/v1/sdk/marshaller.py index dfd18965..b650c26a 100644 --- a/cloudevents/sdk/marshaller.py +++ b/src/cloudevents/v1/sdk/marshaller.py @@ -15,9 +15,9 @@ import json import typing -from cloudevents.sdk import exceptions, types -from cloudevents.sdk.converters import base, binary, structured -from cloudevents.sdk.event import base as event_base +from cloudevents_v1.sdk import exceptions, types +from cloudevents_v1.sdk.converters import base, binary, structured +from cloudevents_v1.sdk.event import base as event_base class HTTPMarshaller(object): diff --git a/cloudevents/sdk/types.py b/src/cloudevents/v1/sdk/types.py similarity index 100% rename from cloudevents/sdk/types.py rename to src/cloudevents/v1/sdk/types.py diff --git a/cloudevents/tests/__init__.py b/src/cloudevents/v1/tests/__init__.py similarity index 100% rename from cloudevents/tests/__init__.py rename to src/cloudevents/v1/tests/__init__.py diff --git a/cloudevents/tests/data.py b/src/cloudevents/v1/tests/data.py similarity index 97% rename from cloudevents/tests/data.py rename to src/cloudevents/v1/tests/data.py index f5b0ea33..a7866c94 100644 --- a/cloudevents/tests/data.py +++ b/src/cloudevents/v1/tests/data.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.sdk.event import v1, v03 +from cloudevents_v1.sdk.event import v03, v1 content_type = "application/json" ce_type = "word.found.exclamation" diff --git a/cloudevents/tests/test_backwards_compatability.py b/src/cloudevents/v1/tests/test_backwards_compatability.py similarity index 62% rename from cloudevents/tests/test_backwards_compatability.py rename to src/cloudevents/v1/tests/test_backwards_compatability.py index 0a20f4cf..157b0dfe 100644 --- a/cloudevents/tests/test_backwards_compatability.py +++ b/src/cloudevents/v1/tests/test_backwards_compatability.py @@ -12,9 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. import pytest - -from cloudevents.conversion import _best_effort_serialize_to_json -from cloudevents.http import CloudEvent +from cloudevents_v1.conversion import _best_effort_serialize_to_json +from cloudevents_v1.http import CloudEvent @pytest.fixture() @@ -23,10 +22,10 @@ def dummy_event(): def test_json_methods(dummy_event): - from cloudevents.conversion import to_json - from cloudevents.http.conversion import from_json - from cloudevents.http.json_methods import from_json as deprecated_from_json - from cloudevents.http.json_methods import to_json as deprecated_to_json + from cloudevents_v1.conversion import to_json + from cloudevents_v1.http.conversion import from_json + from cloudevents_v1.http.json_methods import from_json as deprecated_from_json + from cloudevents_v1.http.json_methods import to_json as deprecated_to_json assert from_json(to_json(dummy_event)) == deprecated_from_json( deprecated_to_json(dummy_event) @@ -34,10 +33,12 @@ def test_json_methods(dummy_event): def test_http_methods(dummy_event): - from cloudevents.http import from_http, to_binary, to_structured - from cloudevents.http.http_methods import from_http as deprecated_from_http - from cloudevents.http.http_methods import to_binary as deprecated_to_binary - from cloudevents.http.http_methods import to_structured as deprecated_to_structured + from cloudevents_v1.http import from_http, to_binary, to_structured + from cloudevents_v1.http.http_methods import from_http as deprecated_from_http + from cloudevents_v1.http.http_methods import to_binary as deprecated_to_binary + from cloudevents_v1.http.http_methods import ( + to_structured as deprecated_to_structured, + ) assert from_http(*to_binary(dummy_event)) == deprecated_from_http( *deprecated_to_binary(dummy_event) @@ -48,17 +49,17 @@ def test_http_methods(dummy_event): def test_util(): - from cloudevents.http.util import default_marshaller # noqa + from cloudevents_v1.http.util import default_marshaller # noqa assert _best_effort_serialize_to_json(None) == default_marshaller(None) def test_event_type(): - from cloudevents.http.event_type import is_binary, is_structured # noqa + from cloudevents_v1.http.event_type import is_binary, is_structured # noqa def test_http_module_imports(): - from cloudevents.http import ( # noqa + from cloudevents_v1.http import ( # noqa CloudEvent, from_dict, from_http, diff --git a/cloudevents/tests/test_base_events.py b/src/cloudevents/v1/tests/test_base_events.py similarity index 92% rename from cloudevents/tests/test_base_events.py rename to src/cloudevents/v1/tests/test_base_events.py index 8eb83d44..04db6470 100644 --- a/cloudevents/tests/test_base_events.py +++ b/src/cloudevents/v1/tests/test_base_events.py @@ -12,10 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +import cloudevents_v1.exceptions as cloud_exceptions import pytest - -import cloudevents.exceptions as cloud_exceptions -from cloudevents.sdk.event import v1, v03 +from cloudevents_v1.sdk.event import v03, v1 @pytest.mark.parametrize("event_class", [v1.Event, v03.Event]) diff --git a/cloudevents/tests/test_converters.py b/src/cloudevents/v1/tests/test_converters.py similarity index 93% rename from cloudevents/tests/test_converters.py rename to src/cloudevents/v1/tests/test_converters.py index b91d6b39..f9940409 100644 --- a/cloudevents/tests/test_converters.py +++ b/src/cloudevents/v1/tests/test_converters.py @@ -13,9 +13,8 @@ # under the License. import pytest - -from cloudevents.sdk import exceptions -from cloudevents.sdk.converters import base, binary +from cloudevents_v1.sdk import exceptions +from cloudevents_v1.sdk.converters import base, binary def test_binary_converter_raise_unsupported(): diff --git a/cloudevents/tests/test_data_encaps_refs.py b/src/cloudevents/v1/tests/test_data_encaps_refs.py similarity index 96% rename from cloudevents/tests/test_data_encaps_refs.py rename to src/cloudevents/v1/tests/test_data_encaps_refs.py index 02405a93..cf923a9c 100644 --- a/cloudevents/tests/test_data_encaps_refs.py +++ b/src/cloudevents/v1/tests/test_data_encaps_refs.py @@ -16,10 +16,9 @@ from uuid import uuid4 import pytest - -from cloudevents.sdk import converters, marshaller -from cloudevents.sdk.event import v1, v03 -from cloudevents.tests import data +from cloudevents_v1.sdk import converters, marshaller +from cloudevents_v1.sdk.event import v03, v1 +from cloudevents_v1.tests import data @pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) diff --git a/cloudevents/tests/test_deprecated_functions.py b/src/cloudevents/v1/tests/test_deprecated_functions.py similarity index 97% rename from cloudevents/tests/test_deprecated_functions.py rename to src/cloudevents/v1/tests/test_deprecated_functions.py index a99f6247..eec0f527 100644 --- a/cloudevents/tests/test_deprecated_functions.py +++ b/src/cloudevents/v1/tests/test_deprecated_functions.py @@ -13,8 +13,7 @@ # under the License. import pytest - -from cloudevents.http import ( +from cloudevents_v1.http import ( CloudEvent, to_binary, to_binary_http, diff --git a/cloudevents/tests/test_event_extensions.py b/src/cloudevents/v1/tests/test_event_extensions.py similarity index 97% rename from cloudevents/tests/test_event_extensions.py rename to src/cloudevents/v1/tests/test_event_extensions.py index eea8edfa..87424090 100644 --- a/cloudevents/tests/test_event_extensions.py +++ b/src/cloudevents/v1/tests/test_event_extensions.py @@ -15,8 +15,7 @@ import json import pytest - -from cloudevents.http import CloudEvent, from_http, to_binary, to_structured +from cloudevents_v1.http import CloudEvent, from_http, to_binary, to_structured test_data = json.dumps({"data-key": "val"}) test_attributes = { diff --git a/cloudevents/tests/test_event_from_request_converter.py b/src/cloudevents/v1/tests/test_event_from_request_converter.py similarity index 93% rename from cloudevents/tests/test_event_from_request_converter.py rename to src/cloudevents/v1/tests/test_event_from_request_converter.py index 901284bb..2f98a640 100644 --- a/cloudevents/tests/test_event_from_request_converter.py +++ b/src/cloudevents/v1/tests/test_event_from_request_converter.py @@ -15,11 +15,10 @@ import json import pytest - -from cloudevents.sdk import marshaller -from cloudevents.sdk.converters import binary, structured -from cloudevents.sdk.event import v1, v03 -from cloudevents.tests import data +from cloudevents_v1.sdk import marshaller +from cloudevents_v1.sdk.converters import binary, structured +from cloudevents_v1.sdk.event import v03, v1 +from cloudevents_v1.tests import data @pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) diff --git a/cloudevents/tests/test_event_pipeline.py b/src/cloudevents/v1/tests/test_event_pipeline.py similarity index 94% rename from cloudevents/tests/test_event_pipeline.py rename to src/cloudevents/v1/tests/test_event_pipeline.py index efc79749..fdb547d5 100644 --- a/cloudevents/tests/test_event_pipeline.py +++ b/src/cloudevents/v1/tests/test_event_pipeline.py @@ -15,11 +15,10 @@ import json import pytest - -from cloudevents.sdk import converters, marshaller -from cloudevents.sdk.converters import structured -from cloudevents.sdk.event import v1, v03 -from cloudevents.tests import data +from cloudevents_v1.sdk import converters, marshaller +from cloudevents_v1.sdk.converters import structured +from cloudevents_v1.sdk.event import v03, v1 +from cloudevents_v1.tests import data @pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) diff --git a/cloudevents/tests/test_event_to_request_converter.py b/src/cloudevents/v1/tests/test_event_to_request_converter.py similarity index 93% rename from cloudevents/tests/test_event_to_request_converter.py rename to src/cloudevents/v1/tests/test_event_to_request_converter.py index fd25be5a..c7fb7022 100644 --- a/cloudevents/tests/test_event_to_request_converter.py +++ b/src/cloudevents/v1/tests/test_event_to_request_converter.py @@ -15,10 +15,9 @@ import json import pytest - -from cloudevents.sdk import converters, marshaller -from cloudevents.sdk.event import v1, v03 -from cloudevents.tests import data +from cloudevents_v1.sdk import converters, marshaller +from cloudevents_v1.sdk.event import v03, v1 +from cloudevents_v1.tests import data @pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) diff --git a/cloudevents/tests/test_http_cloudevent.py b/src/cloudevents/v1/tests/test_http_cloudevent.py similarity index 97% rename from cloudevents/tests/test_http_cloudevent.py rename to src/cloudevents/v1/tests/test_http_cloudevent.py index 6ad1537f..0c68c15c 100644 --- a/cloudevents/tests/test_http_cloudevent.py +++ b/src/cloudevents/v1/tests/test_http_cloudevent.py @@ -12,11 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. +import cloudevents_v1.exceptions as cloud_exceptions import pytest - -import cloudevents.exceptions as cloud_exceptions -from cloudevents.conversion import _json_or_string -from cloudevents.http import CloudEvent +from cloudevents_v1.conversion import _json_or_string +from cloudevents_v1.http import CloudEvent @pytest.fixture(params=["0.3", "1.0"]) diff --git a/cloudevents/tests/test_http_conversions.py b/src/cloudevents/v1/tests/test_http_conversions.py similarity index 96% rename from cloudevents/tests/test_http_conversions.py rename to src/cloudevents/v1/tests/test_http_conversions.py index 3b9c6717..db582a21 100644 --- a/cloudevents/tests/test_http_conversions.py +++ b/src/cloudevents/v1/tests/test_http_conversions.py @@ -17,10 +17,9 @@ import json import pytest - -from cloudevents.conversion import to_dict, to_json -from cloudevents.http import CloudEvent, from_dict, from_json -from cloudevents.sdk.event.attribute import SpecVersion +from cloudevents_v1.conversion import to_dict, to_json +from cloudevents_v1.http import CloudEvent, from_dict, from_json +from cloudevents_v1.sdk.event.attribute import SpecVersion test_data = json.dumps({"data-key": "val"}) test_attributes = { diff --git a/cloudevents/tests/test_http_events.py b/src/cloudevents/v1/tests/test_http_events.py similarity index 97% rename from cloudevents/tests/test_http_events.py rename to src/cloudevents/v1/tests/test_http_events.py index b21c3729..6956df2a 100644 --- a/cloudevents/tests/test_http_events.py +++ b/src/cloudevents/v1/tests/test_http_events.py @@ -17,17 +17,16 @@ import json import typing +import cloudevents_v1.exceptions as cloud_exceptions import pytest +from cloudevents_v1.http import CloudEvent, from_http, to_binary, to_structured +from cloudevents_v1.http.event_type import is_binary as deprecated_is_binary +from cloudevents_v1.http.event_type import is_structured as deprecated_is_structured +from cloudevents_v1.sdk import converters +from cloudevents_v1.sdk.converters.binary import is_binary +from cloudevents_v1.sdk.converters.structured import is_structured from sanic import Sanic, response -import cloudevents.exceptions as cloud_exceptions -from cloudevents.http import CloudEvent, from_http, to_binary, to_structured -from cloudevents.http.event_type import is_binary as deprecated_is_binary -from cloudevents.http.event_type import is_structured as deprecated_is_structured -from cloudevents.sdk import converters -from cloudevents.sdk.converters.binary import is_binary -from cloudevents.sdk.converters.structured import is_structured - invalid_test_headers = [ { "ce-source": "", diff --git a/cloudevents/tests/test_kafka_conversions.py b/src/cloudevents/v1/tests/test_kafka_conversions.py similarity index 98% rename from cloudevents/tests/test_kafka_conversions.py rename to src/cloudevents/v1/tests/test_kafka_conversions.py index 5580773a..661aebbc 100644 --- a/cloudevents/tests/test_kafka_conversions.py +++ b/src/cloudevents/v1/tests/test_kafka_conversions.py @@ -17,18 +17,17 @@ import json import pytest - -from cloudevents import exceptions as cloud_exceptions -from cloudevents.http import CloudEvent -from cloudevents.kafka.conversion import ( +from cloudevents_v1 import exceptions as cloud_exceptions +from cloudevents_v1.http import CloudEvent +from cloudevents_v1.kafka.conversion import ( KafkaMessage, from_binary, from_structured, to_binary, to_structured, ) -from cloudevents.kafka.exceptions import KeyMapperError -from cloudevents.sdk import types +from cloudevents_v1.kafka.exceptions import KeyMapperError +from cloudevents_v1.sdk import types def simple_serialize(data: dict) -> bytes: diff --git a/cloudevents/tests/test_marshaller.py b/src/cloudevents/v1/tests/test_marshaller.py similarity index 93% rename from cloudevents/tests/test_marshaller.py rename to src/cloudevents/v1/tests/test_marshaller.py index 90609891..d3ba81a7 100644 --- a/cloudevents/tests/test_marshaller.py +++ b/src/cloudevents/v1/tests/test_marshaller.py @@ -14,13 +14,12 @@ import json +import cloudevents_v1.exceptions as cloud_exceptions import pytest - -import cloudevents.exceptions as cloud_exceptions -from cloudevents.http import CloudEvent, from_http, to_binary, to_structured -from cloudevents.sdk import exceptions, marshaller -from cloudevents.sdk.converters import binary, structured -from cloudevents.sdk.event import v1 +from cloudevents_v1.http import CloudEvent, from_http, to_binary, to_structured +from cloudevents_v1.sdk import exceptions, marshaller +from cloudevents_v1.sdk.converters import binary, structured +from cloudevents_v1.sdk.event import v1 @pytest.fixture diff --git a/cloudevents/tests/test_options.py b/src/cloudevents/v1/tests/test_options.py similarity index 95% rename from cloudevents/tests/test_options.py rename to src/cloudevents/v1/tests/test_options.py index aba812b9..86b9ef4c 100644 --- a/cloudevents/tests/test_options.py +++ b/src/cloudevents/v1/tests/test_options.py @@ -13,8 +13,7 @@ # under the License. import pytest - -from cloudevents.sdk.event.opt import Option +from cloudevents_v1.sdk.event.opt import Option def test_set_raise_error(): diff --git a/cloudevents/tests/test_pydantic_cloudevent.py b/src/cloudevents/v1/tests/test_pydantic_cloudevent.py similarity index 97% rename from cloudevents/tests/test_pydantic_cloudevent.py rename to src/cloudevents/v1/tests/test_pydantic_cloudevent.py index 87ac5507..4a2762a5 100644 --- a/cloudevents/tests/test_pydantic_cloudevent.py +++ b/src/cloudevents/v1/tests/test_pydantic_cloudevent.py @@ -15,15 +15,14 @@ from json import loads import pytest +from cloudevents_v1.conversion import _json_or_string +from cloudevents_v1.exceptions import IncompatibleArgumentsError +from cloudevents_v1.pydantic.v1.event import CloudEvent as PydanticV1CloudEvent +from cloudevents_v1.pydantic.v2.event import CloudEvent as PydanticV2CloudEvent +from cloudevents_v1.sdk.event.attribute import SpecVersion from pydantic import ValidationError as PydanticV2ValidationError from pydantic.v1 import ValidationError as PydanticV1ValidationError -from cloudevents.conversion import _json_or_string -from cloudevents.exceptions import IncompatibleArgumentsError -from cloudevents.pydantic.v1.event import CloudEvent as PydanticV1CloudEvent -from cloudevents.pydantic.v2.event import CloudEvent as PydanticV2CloudEvent -from cloudevents.sdk.event.attribute import SpecVersion - _DUMMY_SOURCE = "dummy:source" _DUMMY_TYPE = "tests.cloudevents.override" _DUMMY_TIME = "2022-07-16T11:20:34.284130+00:00" diff --git a/cloudevents/tests/test_pydantic_conversions.py b/src/cloudevents/v1/tests/test_pydantic_conversions.py similarity index 90% rename from cloudevents/tests/test_pydantic_conversions.py rename to src/cloudevents/v1/tests/test_pydantic_conversions.py index 801b76bd..abf5cf6e 100644 --- a/cloudevents/tests/test_pydantic_conversions.py +++ b/src/cloudevents/v1/tests/test_pydantic_conversions.py @@ -17,18 +17,17 @@ import json import pytest +from cloudevents_v1.conversion import to_json +from cloudevents_v1.pydantic.v1.conversion import from_dict as pydantic_v1_from_dict +from cloudevents_v1.pydantic.v1.conversion import from_json as pydantic_v1_from_json +from cloudevents_v1.pydantic.v1.event import CloudEvent as PydanticV1CloudEvent +from cloudevents_v1.pydantic.v2.conversion import from_dict as pydantic_v2_from_dict +from cloudevents_v1.pydantic.v2.conversion import from_json as pydantic_v2_from_json +from cloudevents_v1.pydantic.v2.event import CloudEvent as PydanticV2CloudEvent +from cloudevents_v1.sdk.event.attribute import SpecVersion from pydantic import ValidationError as PydanticV2ValidationError from pydantic.v1 import ValidationError as PydanticV1ValidationError -from cloudevents.conversion import to_json -from cloudevents.pydantic.v1.conversion import from_dict as pydantic_v1_from_dict -from cloudevents.pydantic.v1.conversion import from_json as pydantic_v1_from_json -from cloudevents.pydantic.v1.event import CloudEvent as PydanticV1CloudEvent -from cloudevents.pydantic.v2.conversion import from_dict as pydantic_v2_from_dict -from cloudevents.pydantic.v2.conversion import from_json as pydantic_v2_from_json -from cloudevents.pydantic.v2.event import CloudEvent as PydanticV2CloudEvent -from cloudevents.sdk.event.attribute import SpecVersion - test_data = json.dumps({"data-key": "val"}) test_attributes = { "type": "com.example.string", diff --git a/cloudevents/tests/test_pydantic_events.py b/src/cloudevents/v1/tests/test_pydantic_events.py similarity index 96% rename from cloudevents/tests/test_pydantic_events.py rename to src/cloudevents/v1/tests/test_pydantic_events.py index 3e536f05..3b1921cf 100644 --- a/cloudevents/tests/test_pydantic_events.py +++ b/src/cloudevents/v1/tests/test_pydantic_events.py @@ -17,21 +17,20 @@ import json import typing +import cloudevents_v1.exceptions as cloud_exceptions import pytest +from cloudevents_v1.conversion import to_binary, to_structured +from cloudevents_v1.pydantic.v1.conversion import from_http as pydantic_v1_from_http +from cloudevents_v1.pydantic.v1.event import CloudEvent as PydanticV1CloudEvent +from cloudevents_v1.pydantic.v2.conversion import from_http as pydantic_v2_from_http +from cloudevents_v1.pydantic.v2.event import CloudEvent as PydanticV2CloudEvent +from cloudevents_v1.sdk import converters +from cloudevents_v1.sdk.converters.binary import is_binary +from cloudevents_v1.sdk.converters.structured import is_structured from pydantic import ValidationError as PydanticV2ValidationError from pydantic.v1 import ValidationError as PydanticV1ValidationError from sanic import Sanic, response -import cloudevents.exceptions as cloud_exceptions -from cloudevents.conversion import to_binary, to_structured -from cloudevents.pydantic.v1.conversion import from_http as pydantic_v1_from_http -from cloudevents.pydantic.v1.event import CloudEvent as PydanticV1CloudEvent -from cloudevents.pydantic.v2.conversion import from_http as pydantic_v2_from_http -from cloudevents.pydantic.v2.event import CloudEvent as PydanticV2CloudEvent -from cloudevents.sdk import converters -from cloudevents.sdk.converters.binary import is_binary -from cloudevents.sdk.converters.structured import is_structured - invalid_test_headers = [ { "ce-source": "", diff --git a/cloudevents/tests/test_v03_event.py b/src/cloudevents/v1/tests/test_v03_event.py similarity index 97% rename from cloudevents/tests/test_v03_event.py rename to src/cloudevents/v1/tests/test_v03_event.py index a4755318..a66ebe20 100644 --- a/cloudevents/tests/test_v03_event.py +++ b/src/cloudevents/v1/tests/test_v03_event.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.sdk.event import v03 +from cloudevents_v1.sdk.event import v03 def test_v03_time_property(): diff --git a/cloudevents/tests/test_v1_event.py b/src/cloudevents/v1/tests/test_v1_event.py similarity index 97% rename from cloudevents/tests/test_v1_event.py rename to src/cloudevents/v1/tests/test_v1_event.py index de900b0a..e72ecf47 100644 --- a/cloudevents/tests/test_v1_event.py +++ b/src/cloudevents/v1/tests/test_v1_event.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.sdk.event import v1 +from cloudevents_v1.sdk.event import v1 def test_v1_time_property(): diff --git a/cloudevents/tests/test_with_sanic.py b/src/cloudevents/v1/tests/test_with_sanic.py similarity index 93% rename from cloudevents/tests/test_with_sanic.py rename to src/cloudevents/v1/tests/test_with_sanic.py index 026f55b7..2f6d788e 100644 --- a/cloudevents/tests/test_with_sanic.py +++ b/src/cloudevents/v1/tests/test_with_sanic.py @@ -12,12 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. +from cloudevents_v1.sdk import converters, marshaller +from cloudevents_v1.sdk.event import v1 +from cloudevents_v1.tests import data as test_data from sanic import Sanic, response -from cloudevents.sdk import converters, marshaller -from cloudevents.sdk.event import v1 -from cloudevents.tests import data as test_data - m = marshaller.NewDefaultHTTPMarshaller() app = Sanic("test_with_sanic") diff --git a/tests/test_cloudevents/__init__.py b/tests/test_cloudevents/__init__.py new file mode 100644 index 00000000..8043675e --- /dev/null +++ b/tests/test_cloudevents/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/tests/test_cloudevents/test_cloudevents_version.py b/tests/test_cloudevents/test_cloudevents_version.py new file mode 100644 index 00000000..d895c5f5 --- /dev/null +++ b/tests/test_cloudevents/test_cloudevents_version.py @@ -0,0 +1,19 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cloudevents import __version__ + + +def test_cloudevents_version() -> None: + assert __version__ is not None diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..ae08d830 --- /dev/null +++ b/uv.lock @@ -0,0 +1,484 @@ +version = 1 +requires-python = ">=3.9" + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "cloudevents" +version = "2.0.0a1" +source = { editable = "." } +dependencies = [ + { name = "ruff" }, +] + +[package.dev-dependencies] +dev = [ + { name = "flake8" }, + { name = "flake8-print" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pep8-naming" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [{ name = "ruff", specifier = ">=0.6.8" }] + +[package.metadata.requires-dev] +dev = [ + { name = "flake8", specifier = ">=7.1.1" }, + { name = "flake8-print", specifier = ">=5.0.0" }, + { name = "isort", specifier = ">=5.13.2" }, + { name = "mypy", specifier = ">=1.11.2" }, + { name = "pep8-naming", specifier = ">=0.14.1" }, + { name = "pre-commit", specifier = ">=3.8.0" }, + { name = "pytest", specifier = ">=8.3.3" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690 }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127 }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654 }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598 }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732 }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816 }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325 }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418 }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343 }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136 }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796 }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244 }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279 }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859 }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549 }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477 }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910 }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348 }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455 }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 }, + { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688 }, + { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120 }, + { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249 }, + { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237 }, + { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311 }, + { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453 }, + { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958 }, + { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938 }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352 }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153 }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "distlib" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/91/e2df406fb4efacdf46871c25cde65d3c6ee5e173b7e5a4547a47bae91920/distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64", size = 609931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", size = 468850 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "flake8" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/72/e8d66150c4fcace3c0a450466aa3480506ba2cae7b61e100a2613afc3907/flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", size = 48054 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/42/65004373ac4617464f35ed15931b30d764f53cdd30cc78d5aea349c8c050/flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213", size = 57731 }, +] + +[[package]] +name = "flake8-print" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, + { name = "pycodestyle" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/a6/770c5832a6b563e023def7d81925d1b9f3079ebc805e48be0a5ee206f716/flake8-print-5.0.0.tar.gz", hash = "sha256:76915a2a389cc1c0879636c219eb909c38501d3a43cc8dae542081c9ba48bdf9", size = 5166 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/2c/aa2ffda404b5d9c89dad8bcc4e0f4af673ab2de67e96997d13f04ad68b5b/flake8_print-5.0.0-py3-none-any.whl", hash = "sha256:84a1a6ea10d7056b804221ac5e62b1cee1aefc897ce16f2e5c42d3046068f5d8", size = 5687 }, +] + +[[package]] +name = "identify" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "mypy" +version = "1.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/cd/815368cd83c3a31873e5e55b317551500b12f2d1d7549720632f32630333/mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", size = 10939401 }, + { url = "https://files.pythonhosted.org/packages/f1/27/e18c93a195d2fad75eb96e1f1cbc431842c332e8eba2e2b77eaf7313c6b7/mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", size = 10111697 }, + { url = "https://files.pythonhosted.org/packages/dc/08/cdc1fc6d0d5a67d354741344cc4aa7d53f7128902ebcbe699ddd4f15a61c/mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", size = 12500508 }, + { url = "https://files.pythonhosted.org/packages/64/12/aad3af008c92c2d5d0720ea3b6674ba94a98cdb86888d389acdb5f218c30/mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", size = 13020712 }, + { url = "https://files.pythonhosted.org/packages/03/e6/a7d97cc124a565be5e9b7d5c2a6ebf082379ffba99646e4863ed5bbcb3c3/mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", size = 9567319 }, + { url = "https://files.pythonhosted.org/packages/e2/aa/cc56fb53ebe14c64f1fe91d32d838d6f4db948b9494e200d2f61b820b85d/mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", size = 10859630 }, + { url = "https://files.pythonhosted.org/packages/04/c8/b19a760fab491c22c51975cf74e3d253b8c8ce2be7afaa2490fbf95a8c59/mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", size = 10037973 }, + { url = "https://files.pythonhosted.org/packages/88/57/7e7e39f2619c8f74a22efb9a4c4eff32b09d3798335625a124436d121d89/mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", size = 12416659 }, + { url = "https://files.pythonhosted.org/packages/fc/a6/37f7544666b63a27e46c48f49caeee388bf3ce95f9c570eb5cfba5234405/mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", size = 12897010 }, + { url = "https://files.pythonhosted.org/packages/84/8b/459a513badc4d34acb31c736a0101c22d2bd0697b969796ad93294165cfb/mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", size = 9562873 }, + { url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 }, + { url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 }, + { url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 }, + { url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 }, + { url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 }, + { url = "https://files.pythonhosted.org/packages/16/64/bb5ed751487e2bea0dfaa6f640a7e3bb88083648f522e766d5ef4a76f578/mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", size = 10937294 }, + { url = "https://files.pythonhosted.org/packages/a9/a3/67a0069abed93c3bf3b0bebb8857e2979a02828a4a3fd82f107f8f1143e8/mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", size = 10107707 }, + { url = "https://files.pythonhosted.org/packages/2f/4d/0379daf4258b454b1f9ed589a9dabd072c17f97496daea7b72fdacf7c248/mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d", size = 12498367 }, + { url = "https://files.pythonhosted.org/packages/3b/dc/3976a988c280b3571b8eb6928882dc4b723a403b21735a6d8ae6ed20e82b/mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", size = 13018014 }, + { url = "https://files.pythonhosted.org/packages/83/84/adffc7138fb970e7e2a167bd20b33bb78958370179853a4ebe9008139342/mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", size = 9568056 }, + { url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "pep8-naming" +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/8e/1de32e908d8b008bb9352bfe7749aedecb71e2793d36c7ee342716acd1ec/pep8-naming-0.14.1.tar.gz", hash = "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36", size = 16546 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/a2/450b71d1a87fcee50a7b994a53b1c68fc6a6b718df0eb035f2bffb2d3a4f/pep8_naming-0.14.1-py3-none-any.whl", hash = "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5", size = 8859 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643 }, +] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284 }, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "ruff" +version = "0.6.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/f9/4ce3e765a72ab8fe0f80f48508ea38b4196daab3da14d803c21349b2d367/ruff-0.6.8.tar.gz", hash = "sha256:a5bf44b1aa0adaf6d9d20f86162b34f7c593bfedabc51239953e446aefc8ce18", size = 3084543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/07/42ee57e8b76ca585297a663a552b4f6d6a99372ca47fdc2276ef72cc0f2f/ruff-0.6.8-py3-none-linux_armv6l.whl", hash = "sha256:77944bca110ff0a43b768f05a529fecd0706aac7bcce36d7f1eeb4cbfca5f0f2", size = 10404327 }, + { url = "https://files.pythonhosted.org/packages/eb/51/d42571ff8156d65086acb72d39aa64cb24181db53b497d0ed6293f43f07a/ruff-0.6.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27b87e1801e786cd6ede4ada3faa5e254ce774de835e6723fd94551464c56b8c", size = 10018797 }, + { url = "https://files.pythonhosted.org/packages/c1/d7/fa5514a60b03976af972b67fe345deb0335dc96b9f9a9fa4df9890472427/ruff-0.6.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd48f945da2a6334f1793d7f701725a76ba93bf3d73c36f6b21fb04d5338dcf5", size = 9691303 }, + { url = "https://files.pythonhosted.org/packages/d6/c4/d812a74976927e51d0782a47539069657ac78535779bfa4d061c4fc8d89d/ruff-0.6.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:677e03c00f37c66cea033274295a983c7c546edea5043d0c798833adf4cf4c6f", size = 10719452 }, + { url = "https://files.pythonhosted.org/packages/ec/b6/aa700c4ae6db9b3ee660e23f3c7db596e2b16a3034b797704fba33ddbc96/ruff-0.6.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1476236b3eacfacfc0f66aa9e6cd39f2a624cb73ea99189556015f27c0bdeb", size = 10161353 }, + { url = "https://files.pythonhosted.org/packages/ea/39/0b10075ffcd52ff3a581b9b69eac53579deb230aad300ce8f9d0b58e77bc/ruff-0.6.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5a2f17c7d32991169195d52a04c95b256378bbf0de8cb98478351eb70d526f", size = 10980630 }, + { url = "https://files.pythonhosted.org/packages/c1/af/9eb9efc98334f62652e2f9318f137b2667187851911fac3b395365a83708/ruff-0.6.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5fd0d4b7b1457c49e435ee1e437900ced9b35cb8dc5178921dfb7d98d65a08d0", size = 11768996 }, + { url = "https://files.pythonhosted.org/packages/e0/59/8b1369cf7878358952b1c0a1559b4d6b5c824c003d09b0db26d26c9d094f/ruff-0.6.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8034b19b993e9601f2ddf2c517451e17a6ab5cdb1c13fdff50c1442a7171d87", size = 11317469 }, + { url = "https://files.pythonhosted.org/packages/b9/6d/e252e9b11bbca4114c386ee41ad559d0dac13246201d77ea1223c6fea17f/ruff-0.6.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cfb227b932ba8ef6e56c9f875d987973cd5e35bc5d05f5abf045af78ad8e098", size = 12467185 }, + { url = "https://files.pythonhosted.org/packages/48/44/7caa223af7d4ea0f0b2bd34acca65a7694a58317714675a2478815ab3f45/ruff-0.6.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef0411eccfc3909269fed47c61ffebdcb84a04504bafa6b6df9b85c27e813b0", size = 10887766 }, + { url = "https://files.pythonhosted.org/packages/81/ed/394aff3a785f171869158b9d5be61eec9ffb823c3ad5d2bdf2e5f13cb029/ruff-0.6.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:007dee844738c3d2e6c24ab5bc7d43c99ba3e1943bd2d95d598582e9c1b27750", size = 10711609 }, + { url = "https://files.pythonhosted.org/packages/47/31/f31d04c842e54699eab7e3b864538fea26e6c94b71806cd10aa49f13e1c1/ruff-0.6.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ce60058d3cdd8490e5e5471ef086b3f1e90ab872b548814e35930e21d848c9ce", size = 10237621 }, + { url = "https://files.pythonhosted.org/packages/20/95/a764e84acf11d425f2f23b8b78b4fd715e9c20be4aac157c6414ca859a67/ruff-0.6.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1085c455d1b3fdb8021ad534379c60353b81ba079712bce7a900e834859182fa", size = 10558329 }, + { url = "https://files.pythonhosted.org/packages/2a/76/d4e38846ac9f6dd62dce858a54583911361b5339dcf8f84419241efac93a/ruff-0.6.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:70edf6a93b19481affd287d696d9e311388d808671bc209fb8907b46a8c3af44", size = 10954102 }, + { url = "https://files.pythonhosted.org/packages/e7/36/f18c678da6c69f8d022480f3e8ddce6e4a52e07602c1d212056fbd234f8f/ruff-0.6.8-py3-none-win32.whl", hash = "sha256:792213f7be25316f9b46b854df80a77e0da87ec66691e8f012f887b4a671ab5a", size = 8511090 }, + { url = "https://files.pythonhosted.org/packages/4c/c4/0ca7d8ffa358b109db7d7d045a1a076fd8e5d9cbeae022242d3c060931da/ruff-0.6.8-py3-none-win_amd64.whl", hash = "sha256:ec0517dc0f37cad14a5319ba7bba6e7e339d03fbf967a6d69b0907d61be7a263", size = 9350079 }, + { url = "https://files.pythonhosted.org/packages/d9/bd/a8b0c64945a92eaeeb8d0283f27a726a776a1c9d12734d990c5fc7a1278c/ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc", size = 8669595 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "virtualenv" +version = "20.26.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/40/abc5a766da6b0b2457f819feab8e9203cbeae29327bd241359f866a3da9d/virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48", size = 9372482 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/90/57b8ac0c8a231545adc7698c64c5a36fa7cd8e376c691b9bde877269f2eb/virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2", size = 5999862 }, +] From d601888b8ff71716d0bb02e49f5b76e79d536e77 Mon Sep 17 00:00:00 2001 From: Tudor Plugaru Date: Thu, 14 Nov 2024 11:07:23 +0200 Subject: [PATCH 2/3] fix: remove last mentions of py38 (#244) Signed-off-by: Tudor Plugaru --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 52fb4bb3..f06e1da5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -80,7 +79,7 @@ packages = ["src/cloudevents"] [tool.ruff] line-length = 88 -target-version = "py38" +target-version = "py39" exclude = [ ".bzr", From a73c8702d45772ec1c6e36e3b7b4a5b22997f351 Mon Sep 17 00:00:00 2001 From: Tudor Plugaru Date: Sat, 16 Nov 2024 21:20:57 +0200 Subject: [PATCH 3/3] feat: base `CloudEvent` class as per v1 specs, including attribute validation (#242) * feat: base `CloudEvent` class as per v1 specs, including attribute validation Signed-off-by: Tudor Plugaru * chore: add typings and docstrings Signed-off-by: Tudor Plugaru * chore: Add support for custom extension names and validate them Signed-off-by: Tudor * chore: Add copyright and fix missing type info Signed-off-by: Tudor Plugaru * chore: Add getters for attributes and test happy path Signed-off-by: Tudor Plugaru * fix: typing Signed-off-by: Tudor Plugaru * chore: Split validation logic into smaller methods Signed-off-by: Tudor Plugaru * chore: Add method to extract extension by name Signed-off-by: Tudor Plugaru * chore: configure ruff to sort imports also Signed-off-by: Tudor Plugaru * chore: Returns all the errors at ones instead of raising early. Improve tests Signed-off-by: Tudor Plugaru * fix missing type info Signed-off-by: Tudor Plugaru * chore: Improve exceptions handling. Have exceptions grouped by attribute name and typed exceptions Signed-off-by: Tudor Plugaru * chore: Skip type checing for getters of required attributes We can't use TypedDict here becuase it does not allow for arbitrary keys which we need in order to support custom extension attributes. Signed-off-by: Tudor Plugaru * fix: missing type Signed-off-by: Tudor Plugaru * chore: Improve exceptions and introduce a new one for invalid values Signed-off-by: Tudor Plugaru * fix: str representation for validation error Signed-off-by: Tudor Plugaru * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: Fix missing type definitions Signed-off-by: Tudor Plugaru * small fix Signed-off-by: Tudor Plugaru * remove cast of defaultdict to dict Signed-off-by: Tudor Plugaru --------- Signed-off-by: Tudor Plugaru Signed-off-by: Tudor Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- pyproject.toml | 2 + src/cloudevents/core/v1/__init__.py | 17 ++ src/cloudevents/core/v1/event.py | 324 +++++++++++++++++++++++++ src/cloudevents/core/v1/exceptions.py | 76 ++++++ tests/test_core/__init__.py | 13 + tests/test_core/test_v1/__init__.py | 13 + tests/test_core/test_v1/test_event.py | 333 ++++++++++++++++++++++++++ 7 files changed, 778 insertions(+) create mode 100644 src/cloudevents/core/v1/__init__.py create mode 100644 src/cloudevents/core/v1/event.py create mode 100644 src/cloudevents/core/v1/exceptions.py create mode 100644 tests/test_core/__init__.py create mode 100644 tests/test_core/test_v1/__init__.py create mode 100644 tests/test_core/test_v1/test_event.py diff --git a/pyproject.toml b/pyproject.toml index f06e1da5..5c152bc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,6 +111,8 @@ exclude = [ [tool.ruff.lint] ignore = ["E731"] extend-ignore = ["E203"] +select = ["I"] + [tool.pytest.ini_options] testpaths = [ diff --git a/src/cloudevents/core/v1/__init__.py b/src/cloudevents/core/v1/__init__.py new file mode 100644 index 00000000..896dfe12 --- /dev/null +++ b/src/cloudevents/core/v1/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +CloudEvent implementation for v1.0 +""" diff --git a/src/cloudevents/core/v1/event.py b/src/cloudevents/core/v1/event.py new file mode 100644 index 00000000..043670b5 --- /dev/null +++ b/src/cloudevents/core/v1/event.py @@ -0,0 +1,324 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re +from collections import defaultdict +from datetime import datetime +from typing import Any, Final, Optional + +from cloudevents.core.v1.exceptions import ( + BaseCloudEventException, + CloudEventValidationError, + CustomExtensionAttributeError, + InvalidAttributeTypeError, + InvalidAttributeValueError, + MissingRequiredAttributeError, +) + +REQUIRED_ATTRIBUTES: Final[list[str]] = ["id", "source", "type", "specversion"] +OPTIONAL_ATTRIBUTES: Final[list[str]] = [ + "datacontenttype", + "dataschema", + "subject", + "time", +] + + +class CloudEvent: + """ + The CloudEvent Python wrapper contract exposing generically-available + properties and APIs. + + Implementations might handle fields and have other APIs exposed but are + obliged to follow this contract. + """ + + def __init__(self, attributes: dict[str, Any], data: Optional[dict] = None) -> None: + """ + Create a new CloudEvent instance. + + :param attributes: The attributes of the CloudEvent instance. + :param data: The payload of the CloudEvent instance. + + :raises ValueError: If any of the required attributes are missing or have invalid values. + :raises TypeError: If any of the attributes have invalid types. + """ + self._validate_attribute(attributes=attributes) + self._attributes: dict[str, Any] = attributes + self._data: Optional[dict] = data + + @staticmethod + def _validate_attribute(attributes: dict[str, Any]) -> None: + """ + Validates the attributes of the CloudEvent as per the CloudEvents specification. + + See https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#required-attributes + """ + errors: dict[str, list[BaseCloudEventException]] = defaultdict(list) + errors.update(CloudEvent._validate_required_attributes(attributes=attributes)) + errors.update(CloudEvent._validate_optional_attributes(attributes=attributes)) + errors.update(CloudEvent._validate_extension_attributes(attributes=attributes)) + if errors: + raise CloudEventValidationError(errors=errors) + + @staticmethod + def _validate_required_attributes( + attributes: dict[str, Any], + ) -> dict[str, list[BaseCloudEventException]]: + """ + Validates the types of the required attributes. + + :param attributes: The attributes of the CloudEvent instance. + :return: A dictionary of validation error messages. + """ + errors = defaultdict(list) + + if "id" not in attributes: + errors["id"].append(MissingRequiredAttributeError(attribute_name="id")) + if attributes.get("id") is None: + errors["id"].append( + InvalidAttributeValueError( + attribute_name="id", msg="Attribute 'id' must not be None" + ) + ) + if not isinstance(attributes.get("id"), str): + errors["id"].append( + InvalidAttributeTypeError(attribute_name="id", expected_type=str) + ) + + if "source" not in attributes: + errors["source"].append( + MissingRequiredAttributeError(attribute_name="source") + ) + if not isinstance(attributes.get("source"), str): + errors["source"].append( + InvalidAttributeTypeError(attribute_name="source", expected_type=str) + ) + + if "type" not in attributes: + errors["type"].append(MissingRequiredAttributeError(attribute_name="type")) + if not isinstance(attributes.get("type"), str): + errors["type"].append( + InvalidAttributeTypeError(attribute_name="type", expected_type=str) + ) + + if "specversion" not in attributes: + errors["specversion"].append( + MissingRequiredAttributeError(attribute_name="specversion") + ) + if not isinstance(attributes.get("specversion"), str): + errors["specversion"].append( + InvalidAttributeTypeError( + attribute_name="specversion", expected_type=str + ) + ) + if attributes.get("specversion") != "1.0": + errors["specversion"].append( + InvalidAttributeValueError( + attribute_name="specversion", + msg="Attribute 'specversion' must be '1.0'", + ) + ) + return errors + + @staticmethod + def _validate_optional_attributes( + attributes: dict[str, Any], + ) -> dict[str, list[BaseCloudEventException]]: + """ + Validates the types and values of the optional attributes. + + :param attributes: The attributes of the CloudEvent instance. + :return: A dictionary of validation error messages. + """ + errors = defaultdict(list) + + if "time" in attributes: + if not isinstance(attributes["time"], datetime): + errors["time"].append( + InvalidAttributeTypeError( + attribute_name="time", expected_type=datetime + ) + ) + if hasattr(attributes["time"], "tzinfo") and not attributes["time"].tzinfo: + errors["time"].append( + InvalidAttributeValueError( + attribute_name="time", + msg="Attribute 'time' must be timezone aware", + ) + ) + if "subject" in attributes: + if not isinstance(attributes["subject"], str): + errors["subject"].append( + InvalidAttributeTypeError( + attribute_name="subject", expected_type=str + ) + ) + if not attributes["subject"]: + errors["subject"].append( + InvalidAttributeValueError( + attribute_name="subject", + msg="Attribute 'subject' must not be empty", + ) + ) + if "datacontenttype" in attributes: + if not isinstance(attributes["datacontenttype"], str): + errors["datacontenttype"].append( + InvalidAttributeTypeError( + attribute_name="datacontenttype", expected_type=str + ) + ) + if not attributes["datacontenttype"]: + errors["datacontenttype"].append( + InvalidAttributeValueError( + attribute_name="datacontenttype", + msg="Attribute 'datacontenttype' must not be empty", + ) + ) + if "dataschema" in attributes: + if not isinstance(attributes["dataschema"], str): + errors["dataschema"].append( + InvalidAttributeTypeError( + attribute_name="dataschema", expected_type=str + ) + ) + if not attributes["dataschema"]: + errors["dataschema"].append( + InvalidAttributeValueError( + attribute_name="dataschema", + msg="Attribute 'dataschema' must not be empty", + ) + ) + return errors + + @staticmethod + def _validate_extension_attributes( + attributes: dict[str, Any], + ) -> dict[str, list[BaseCloudEventException]]: + """ + Validates the extension attributes. + + :param attributes: The attributes of the CloudEvent instance. + :return: A dictionary of validation error messages. + """ + errors = defaultdict(list) + extension_attributes = [ + key + for key in attributes.keys() + if key not in REQUIRED_ATTRIBUTES and key not in OPTIONAL_ATTRIBUTES + ] + for extension_attribute in extension_attributes: + if extension_attribute == "data": + errors[extension_attribute].append( + CustomExtensionAttributeError( + attribute_name=extension_attribute, + msg="Extension attribute 'data' is reserved and must not be used", + ) + ) + if not (1 <= len(extension_attribute) <= 20): + errors[extension_attribute].append( + CustomExtensionAttributeError( + attribute_name=extension_attribute, + msg=f"Extension attribute '{extension_attribute}' should be between 1 and 20 characters long", + ) + ) + if not re.match(r"^[a-z0-9]+$", extension_attribute): + errors[extension_attribute].append( + CustomExtensionAttributeError( + attribute_name=extension_attribute, + msg=f"Extension attribute '{extension_attribute}' should only contain lowercase letters and numbers", + ) + ) + return errors + + def get_id(self) -> str: + """ + Retrieve the ID of the event. + + :return: The ID of the event. + """ + return self._attributes["id"] # type: ignore + + def get_source(self) -> str: + """ + Retrieve the source of the event. + + :return: The source of the event. + """ + return self._attributes["source"] # type: ignore + + def get_type(self) -> str: + """ + Retrieve the type of the event. + + :return: The type of the event. + """ + return self._attributes["type"] # type: ignore + + def get_specversion(self) -> str: + """ + Retrieve the specversion of the event. + + :return: The specversion of the event. + """ + return self._attributes["specversion"] # type: ignore + + def get_datacontenttype(self) -> Optional[str]: + """ + Retrieve the datacontenttype of the event. + + :return: The datacontenttype of the event. + """ + return self._attributes.get("datacontenttype") + + def get_dataschema(self) -> Optional[str]: + """ + Retrieve the dataschema of the event. + + :return: The dataschema of the event. + """ + return self._attributes.get("dataschema") + + def get_subject(self) -> Optional[str]: + """ + Retrieve the subject of the event. + + :return: The subject of the event. + """ + return self._attributes.get("subject") + + def get_time(self) -> Optional[datetime]: + """ + Retrieve the time of the event. + + :return: The time of the event. + """ + return self._attributes.get("time") + + def get_extension(self, extension_name: str) -> Any: + """ + Retrieve an extension attribute of the event. + + :param extension_name: The name of the extension attribute. + :return: The value of the extension attribute. + """ + return self._attributes.get(extension_name) + + def get_data(self) -> Optional[dict]: + """ + Retrieve data of the event. + + :return: The data of the event. + """ + return self._data diff --git a/src/cloudevents/core/v1/exceptions.py b/src/cloudevents/core/v1/exceptions.py new file mode 100644 index 00000000..ba6b63ae --- /dev/null +++ b/src/cloudevents/core/v1/exceptions.py @@ -0,0 +1,76 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +class BaseCloudEventException(Exception): + """A CloudEvent generic exception.""" + + +class CloudEventValidationError(BaseCloudEventException): + """ + Holds validation errors aggregated during a CloudEvent creation. + """ + + def __init__(self, errors: dict[str, list[BaseCloudEventException]]) -> None: + """ + :param errors: The errors gathered during the CloudEvent creation where key + is the name of the attribute and value is a list of errors related to that attribute. + """ + super().__init__("Failed to create CloudEvent due to the validation errors:") + self.errors: dict[str, list[BaseCloudEventException]] = errors + + def __str__(self) -> str: + error_messages: list[str] = [ + f"{key}: {', '.join(str(e) for e in value)}" + for key, value in self.errors.items() + ] + return f"{super().__str__()}: {', '.join(error_messages)}" + + +class MissingRequiredAttributeError(BaseCloudEventException, ValueError): + """ + Raised for attributes that are required to be present by the specification. + """ + + def __init__(self, attribute_name: str) -> None: + self.attribute_name: str = attribute_name + super().__init__(f"Missing required attribute: '{attribute_name}'") + + +class CustomExtensionAttributeError(BaseCloudEventException, ValueError): + """ + Raised when a custom extension attribute violates naming conventions. + """ + + def __init__(self, attribute_name: str, msg: str) -> None: + self.attribute_name: str = attribute_name + super().__init__(msg) + + +class InvalidAttributeTypeError(BaseCloudEventException, TypeError): + """ + Raised when an attribute has an unsupported type. + """ + + def __init__(self, attribute_name: str, expected_type: type) -> None: + self.attribute_name: str = attribute_name + super().__init__(f"Attribute '{attribute_name}' must be a {expected_type}") + + +class InvalidAttributeValueError(BaseCloudEventException, ValueError): + """ + Raised when an attribute has an invalid value. + """ + + def __init__(self, attribute_name: str, msg: str) -> None: + self.attribute_name: str = attribute_name + super().__init__(msg) diff --git a/tests/test_core/__init__.py b/tests/test_core/__init__.py new file mode 100644 index 00000000..8043675e --- /dev/null +++ b/tests/test_core/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/tests/test_core/test_v1/__init__.py b/tests/test_core/test_v1/__init__.py new file mode 100644 index 00000000..8043675e --- /dev/null +++ b/tests/test_core/test_v1/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/tests/test_core/test_v1/test_event.py b/tests/test_core/test_v1/test_event.py new file mode 100644 index 00000000..acd3fd2b --- /dev/null +++ b/tests/test_core/test_v1/test_event.py @@ -0,0 +1,333 @@ +# Copyright 2018-Present The CloudEvents Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from datetime import datetime, timezone +from typing import Any + +import pytest + +from cloudevents.core.v1.event import CloudEvent +from cloudevents.core.v1.exceptions import ( + CloudEventValidationError, + CustomExtensionAttributeError, + InvalidAttributeTypeError, + InvalidAttributeValueError, + MissingRequiredAttributeError, +) + + +def test_missing_required_attributes() -> None: + with pytest.raises(CloudEventValidationError) as e: + CloudEvent({}) + + expected_errors = { + "id": [ + str(MissingRequiredAttributeError("id")), + str(InvalidAttributeValueError("id", "Attribute 'id' must not be None")), + str(InvalidAttributeTypeError("id", str)), + ], + "source": [ + str(MissingRequiredAttributeError("source")), + str(InvalidAttributeTypeError("source", str)), + ], + "type": [ + str(MissingRequiredAttributeError("type")), + str(InvalidAttributeTypeError("type", str)), + ], + "specversion": [ + str(MissingRequiredAttributeError("specversion")), + str(InvalidAttributeTypeError("specversion", str)), + str( + InvalidAttributeValueError( + "specversion", "Attribute 'specversion' must be '1.0'" + ) + ), + ], + } + + actual_errors = { + key: [str(e) for e in value] for key, value in e.value.errors.items() + } + assert actual_errors == expected_errors + + +@pytest.mark.parametrize( + "time,expected_error", + [ + ( + "2023-10-25T17:09:19.736166Z", + {"time": [str(InvalidAttributeTypeError("time", datetime))]}, + ), + ( + datetime(2023, 10, 25, 17, 9, 19, 736166), + { + "time": [ + str( + InvalidAttributeValueError( + "time", "Attribute 'time' must be timezone aware" + ) + ) + ] + }, + ), + ( + 1, + {"time": [str(InvalidAttributeTypeError("time", datetime))]}, + ), + ], +) +def test_time_validation(time: Any, expected_error: dict) -> None: + with pytest.raises(CloudEventValidationError) as e: + CloudEvent( + { + "id": "1", + "source": "/", + "type": "test", + "specversion": "1.0", + "time": time, + } + ) + actual_errors = { + key: [str(e) for e in value] for key, value in e.value.errors.items() + } + assert actual_errors == expected_error + + +@pytest.mark.parametrize( + "subject,expected_error", + [ + ( + 1234, + {"subject": [str(InvalidAttributeTypeError("subject", str))]}, + ), + ( + "", + { + "subject": [ + str( + InvalidAttributeValueError( + "subject", "Attribute 'subject' must not be empty" + ) + ) + ] + }, + ), + ], +) +def test_subject_validation(subject: Any, expected_error: dict) -> None: + with pytest.raises(CloudEventValidationError) as e: + CloudEvent( + { + "id": "1", + "source": "/", + "type": "test", + "specversion": "1.0", + "subject": subject, + } + ) + + actual_errors = { + key: [str(e) for e in value] for key, value in e.value.errors.items() + } + assert actual_errors == expected_error + + +@pytest.mark.parametrize( + "datacontenttype,expected_error", + [ + ( + 1234, + { + "datacontenttype": [ + str(InvalidAttributeTypeError("datacontenttype", str)) + ] + }, + ), + ( + "", + { + "datacontenttype": [ + str( + InvalidAttributeValueError( + "datacontenttype", + "Attribute 'datacontenttype' must not be empty", + ) + ) + ] + }, + ), + ], +) +def test_datacontenttype_validation(datacontenttype: Any, expected_error: dict) -> None: + with pytest.raises(CloudEventValidationError) as e: + CloudEvent( + { + "id": "1", + "source": "/", + "type": "test", + "specversion": "1.0", + "datacontenttype": datacontenttype, + } + ) + + actual_errors = { + key: [str(e) for e in value] for key, value in e.value.errors.items() + } + assert actual_errors == expected_error + + +@pytest.mark.parametrize( + "dataschema,expected_error", + [ + ( + 1234, + {"dataschema": [str(InvalidAttributeTypeError("dataschema", str))]}, + ), + ( + "", + { + "dataschema": [ + str( + InvalidAttributeValueError( + "dataschema", "Attribute 'dataschema' must not be empty" + ) + ) + ] + }, + ), + ], +) +def test_dataschema_validation(dataschema: Any, expected_error: dict) -> None: + with pytest.raises(CloudEventValidationError) as e: + CloudEvent( + { + "id": "1", + "source": "/", + "type": "test", + "specversion": "1.0", + "dataschema": dataschema, + } + ) + + actual_errors = { + key: [str(e) for e in value] for key, value in e.value.errors.items() + } + assert actual_errors == expected_error + + +@pytest.mark.parametrize( + "extension_name,expected_error", + [ + ( + "", + { + "": [ + str( + CustomExtensionAttributeError( + "", + "Extension attribute '' should be between 1 and 20 characters long", + ) + ), + str( + CustomExtensionAttributeError( + "", + "Extension attribute '' should only contain lowercase letters and numbers", + ) + ), + ] + }, + ), + ( + "thisisaverylongextension", + { + "thisisaverylongextension": [ + str( + CustomExtensionAttributeError( + "thisisaverylongextension", + "Extension attribute 'thisisaverylongextension' should be between 1 and 20 characters long", + ) + ) + ] + }, + ), + ( + "data", + { + "data": [ + str( + CustomExtensionAttributeError( + "data", + "Extension attribute 'data' is reserved and must not be used", + ) + ) + ] + }, + ), + ], +) +def test_custom_extension(extension_name: str, expected_error: dict) -> None: + with pytest.raises(CloudEventValidationError) as e: + CloudEvent( + { + "id": "1", + "source": "/", + "type": "test", + "specversion": "1.0", + extension_name: "value", + } + ) + + actual_errors = { + key: [str(e) for e in value] for key, value in e.value.errors.items() + } + assert actual_errors == expected_error + + +def test_cloud_event_constructor() -> None: + id = "1" + source = "/source" + type = "com.test.type" + specversion = "1.0" + datacontenttype = "application/json" + dataschema = "http://example.com/schema" + subject = "test_subject" + time = datetime.now(tz=timezone.utc) + data = {"key": "value"} + customextension = "customExtension" + + event = CloudEvent( + attributes={ + "id": id, + "source": source, + "type": type, + "specversion": specversion, + "datacontenttype": datacontenttype, + "dataschema": dataschema, + "subject": subject, + "time": time, + "customextension": customextension, + }, + data=data, + ) + + assert event.get_id() == id + assert event.get_source() == source + assert event.get_type() == type + assert event.get_specversion() == specversion + assert event.get_datacontenttype() == datacontenttype + assert event.get_dataschema() == dataschema + assert event.get_subject() == subject + assert event.get_time() == time + assert event.get_extension("customextension") == customextension + assert event.get_data() == data