diff --git a/.github/workflows/_lint-github-ci.yml b/.github/workflows/_lint-github-ci.yml new file mode 100644 index 0000000..04e9e0f --- /dev/null +++ b/.github/workflows/_lint-github-ci.yml @@ -0,0 +1,19 @@ +name: Lint GitHub CI + +on: + pull_request: + branches: + - main + paths: + - .github/** + - actions/** + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-22.04 + steps: + - name: Lint actions and workflows + uses: bakdata/ci-templates/actions/action-lint@1.46.3 + with: + action-lint-version: "v1.6.27" diff --git a/.github/workflows/pre-commit-auto-update.yml b/.github/workflows/pre-commit-auto-update.yml index e9d8da0..8caf6e6 100644 --- a/.github/workflows/pre-commit-auto-update.yml +++ b/.github/workflows/pre-commit-auto-update.yml @@ -10,12 +10,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install pre-commit run: pip install pre-commit diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 4859a36..a562d38 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -13,19 +13,19 @@ jobs: fail-fast: false matrix: python-version: - - "3.10" + - "3.12" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Install poetry + - name: Install Poetry run: pipx install poetry - - name: Configure poetry virtualenv + - name: Configure Poetry virtualenv run: poetry config virtualenvs.in-project true - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: poetry @@ -35,22 +35,17 @@ jobs: - name: Install dependencies run: poetry install --no-interaction - - name: Lint flake8 - run: | - poetry run pre-commit run --hook-stage manual flake8 --all-files --show-diff-on-failure + - name: Check Poetry config and lockfile + run: poetry run pre-commit run poetry-check --all-files - - name: Check black - run: | - poetry run pre-commit run --hook-stage manual black --all-files --show-diff-on-failure + - name: Lint (ruff) + run: poetry run ruff check . --config pyproject.toml --output-format=github --no-fix - - name: Check mypy - run: | - poetry run pre-commit run --hook-stage manual mypy --all-files + - name: Formatting (ruff) + run: poetry run pre-commit run ruff-format --all-files --show-diff-on-failure - - name: Check syntax update - run: | - poetry run pre-commit run --hook-stage manual pyupgrade --all-files + - name: Typing (pyright) + run: poetry run pre-commit run pyright --all-files - - name: Test pytest - run: | - poetry run pytest + - name: Test + run: poetry run pytest tests diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 27b18fe..a5772b2 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -10,27 +10,27 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Get Python version id: python-version - run: echo ::set-output name=version::"$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")" + run: echo version="$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")" >> "$GITHUB_OUTPUT" - - name: Run poetry image - uses: abatilo/actions-poetry@v3.0.0 + - name: Run Poetry image + uses: abatilo/actions-poetry@v3.0.1 with: poetry-version: "1.1.12" - - name: Set poetry virtualenv + - name: Set Poetry virtualenv run: poetry config virtualenvs.in-project true - name: Set up cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache with: path: .venv @@ -47,8 +47,7 @@ jobs: # - name: Bump version # env: # VERSION: ${{ github.event.release.tag_name }} - # run: | - # poetry version $(echo $VERSION | sed -e 's/^v//') + # run: poetry version $(echo $VERSION | sed -e 's/^v//') - name: Build run: poetry build @@ -58,11 +57,9 @@ jobs: POETRY_REPOSITORIES_TESTPYPI_URL: https://test.pypi.org/legacy/ POETRY_HTTP_BASIC_TESTPYPI_USERNAME: __token__ POETRY_HTTP_BASIC_TESTPYPI_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} - run: | - poetry publish -r testpypi + run: poetry publish -r testpypi - name: Publish PyPI env: POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }} - run: | - poetry publish + run: poetry publish diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ee6e6f..5c2858a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,47 @@ --- repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + - repo: local hooks: - - id: debug-statements - - repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 - hooks: - - id: pyupgrade - stages: [manual] - args: ["--py310-plus"] - - repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - args: ["--settings", "setup.cfg"] - - repo: https://github.com/psf/black - rev: 24.3.0 - hooks: - - id: black - language_version: python3 - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 - hooks: - - id: mypy - args: ["--pretty"] - additional_dependencies: [types-python-dateutil, types-freezegun] - - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - args: ["--config", "setup.cfg"] + - id: poetry-install + name: poetry-install + description: install Poetry dependencies from the lock file + entry: poetry install + language: python + pass_filenames: false + stages: [post-checkout, post-merge] + always_run: true + - id: poetry-check + name: poetry-check + description: validate Poetry config and dependency lock file + entry: poetry check + language: python + pass_filenames: false + files: ^(.*/)?(pyproject\.toml|poetry\.lock)$ + - id: ruff-lint + name: ruff-lint + entry: poetry run ruff check + args: + [ + --force-exclude, + --config, + pyproject.toml, + --fix, + --show-fixes, + --exit-non-zero-on-fix, + ] + language: system + types_or: [python] + require_serial: true # run once for all files + - id: ruff-format + name: ruff-format + entry: poetry run ruff format + args: [--force-exclude, --config, pyproject.toml] + language: system + types_or: [python] + require_serial: true # run once for all files + - id: pyright + name: pyright + entry: poetry run pyright + language: system + types: [python] + require_serial: true # run once for all files diff --git a/README.md b/README.md index b39e968..b3e99a2 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,23 @@ OFFSET = 1234 OFFSET = None things = ThingsClient(ACCOUNT, initial_offset=OFFSET) -# create a project -project = TodoItem("Things Cloud Project").as_project() +# create a new project +project = TodoItem(title="Things Cloud Project").as_project() # push to Things Cloud -things.create(project) +things.commit(project) # create a todo inside project -todo = TodoItem("Try out Things Cloud") +todo = TodoItem(title="Try out Things Cloud") todo.project = project -things.create(todo) +things.commit(todo) # schedule for today todo.today() -things.edit(todo) +things.commit(todo) ``` +See [main.py](main.py). + ### Progress - [x] Todos diff --git a/main.py b/main.py index 7d7b726..83546bc 100644 --- a/main.py +++ b/main.py @@ -15,21 +15,21 @@ def main(): things = ThingsClient(ACCOUNT, initial_offset=OFFSET) - # create a project - project = TodoItem("Things Cloud Project").as_project() - things.create(project) + # create a new project + project = TodoItem(title="Things Cloud Project").as_project() + things.commit(project) log.debug("created project", uuid=project.uuid) sleep(10) # create a todo inside project - todo = TodoItem("Try out Things Cloud") + todo = TodoItem(title="Try out Things Cloud") todo.project = project - things.create(todo) + things.commit(todo) sleep(10) # schedule for today todo.today() - things.edit(todo) + things.commit(todo) if __name__ == "__main__": diff --git a/poetry.lock b/poetry.lock index e15d16a..4ebf06b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,15 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] [[package]] name = "anyio" @@ -22,93 +33,46 @@ trio = ["trio (>=0.16)"] [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "black" -version = "23.12.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, - {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, - {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, - {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, - {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, - {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, - {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, - {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, - {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, - {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, - {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, - {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, - {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, - {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, - {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, - {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, - {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, - {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, - {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, - {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, - {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, - {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "cattrs" -version = "23.1.2" +version = "24.1.2" description = "Composable complex class support for attrs and dataclasses." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "cattrs-23.1.2-py3-none-any.whl", hash = "sha256:b2bb14311ac17bed0d58785e5a60f022e5431aca3932e3fc5cc8ed8639de50a4"}, - {file = "cattrs-23.1.2.tar.gz", hash = "sha256:db1c821b8c537382b2c7c66678c3790091ca0275ac486c76f3c8f3920e83c657"}, + {file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"}, + {file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"}, ] [package.dependencies] -attrs = ">=20" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -typing_extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} +attrs = ">=23.1.0" [package.extras] -bson = ["pymongo (>=4.2.0,<5.0.0)"] -cbor2 = ["cbor2 (>=5.4.6,<6.0.0)"] -msgpack = ["msgpack (>=1.0.2,<2.0.0)"] -orjson = ["orjson (>=3.5.2,<4.0.0)"] -pyyaml = ["PyYAML (>=6.0,<7.0)"] -tomlkit = ["tomlkit (>=0.11.4,<0.12.0)"] -ujson = ["ujson (>=5.4.0,<6.0.0)"] +bson = ["pymongo (>=4.4.0)"] +cbor2 = ["cbor2 (>=5.4.6)"] +msgpack = ["msgpack (>=1.0.5)"] +msgspec = ["msgspec (>=0.18.5)"] +orjson = ["orjson (>=3.9.2)"] +pyyaml = ["pyyaml (>=6.0)"] +tomlkit = ["tomlkit (>=0.11.8)"] +ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" @@ -132,20 +96,6 @@ files = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] -[[package]] -name = "click" -version = "8.0.4" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.6" -files = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.4" @@ -172,70 +122,115 @@ files = [ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] -name = "distlib" -version = "0.3.4" -description = "Distribution utilities" +name = "coverage" +version = "7.6.9" +description = "Code coverage measurement for Python" optional = false -python-versions = "*" +python-versions = ">=3.9" files = [ - {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, - {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, + {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, + {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, + {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, + {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, + {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, + {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, + {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, + {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, + {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, + {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, + {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, + {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, + {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, + {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, ] +[package.extras] +toml = ["tomli"] + [[package]] -name = "exceptiongroup" -version = "1.0.0rc9" -description = "Backport of PEP 654 (exception groups)" +name = "distlib" +version = "0.3.9" +description = "Distribution utilities" optional = false -python-versions = ">=3.7" +python-versions = "*" files = [ - {file = "exceptiongroup-1.0.0rc9-py3-none-any.whl", hash = "sha256:2e3c3fc1538a094aab74fad52d6c33fc94de3dfee3ee01f187c0e0c72aec5337"}, - {file = "exceptiongroup-1.0.0rc9.tar.gz", hash = "sha256:9086a4a21ef9b31c72181c77c040a074ba0889ee56a7b289ff0afb0d97655f96"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "filelock" -version = "3.6.0" +version = "3.16.1" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, - {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] - -[[package]] -name = "flake8" -version = "7.0.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" -pyflakes = ">=3.2.0,<3.3.0" +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "freezegun" -version = "1.2.2" +version = "1.5.1" description = "Let your Python tests travel through time" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, - {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, ] [package.dependencies] @@ -243,58 +238,59 @@ python-dateutil = ">=2.7" [[package]] name = "h11" -version = "0.12.0" +version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, - {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] [[package]] name = "httpcore" -version = "0.15.0" +version = "1.0.7" description = "A minimal low-level HTTP client." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, - {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, ] [package.dependencies] -anyio = "==3.*" certifi = "*" -h11 = ">=0.11,<0.13" -sniffio = "==1.*" +h11 = ">=0.13,<0.15" [package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" -version = "0.23.3" +version = "0.28.1" description = "The next generation HTTP client." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] +anyio = "*" certifi = "*" -httpcore = ">=0.15.0,<0.17.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" +httpcore = "==1.*" +idna = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" @@ -332,92 +328,6 @@ files = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -[[package]] -name = "isort" -version = "5.12.0" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy" -version = "1.8.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "nodeenv" version = "1.6.0" @@ -429,63 +339,105 @@ files = [ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] +[[package]] +name = "nodejs-wheel-binaries" +version = "22.12.0" +description = "unoffical Node.js package" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ed26eac5a96f3bfcd36f1eb9c397f07392e8b166895e0a65eb6033baefa0217"}, + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ca853c919e18860eb75ac3563985391d07eb50f6ab0aadcb48842bcc0ee8dd45"}, + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6df3f2606e26d3334de1fc15c4e6a833980a02ed2eae528d90ddaacaaab2dd1"}, + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:335176ffa5f753c4d447af83f70987f5f82becdf84fadf56d5c0036379826cff"}, + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:44f0b372a9ea218be885d14b370e774366d09697ae193b741b83cf04c6753884"}, + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-win_amd64.whl", hash = "sha256:d1a63fc84b0a5a94b19f428ecc1eb1bf4d09c9fc26390db52231c01c3667fe82"}, + {file = "nodejs_wheel_binaries-22.12.0-py2.py3-none-win_arm64.whl", hash = "sha256:cc363752be784bf6496329060c53c4d8a993dca73815bb179b37dba5c6d6db1c"}, + {file = "nodejs_wheel_binaries-22.12.0.tar.gz", hash = "sha256:e9b707766498322bb2b79fb4f6f425ef86904feccc72cc426f8d96f75488518e"}, +] + [[package]] name = "orjson" -version = "3.9.10" +version = "3.10.12" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c18a4da2f50050a03d1da5317388ef84a16013302a5281d6f64e4a3f406aabc4"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5148bab4d71f58948c7c39d12b14a9005b6ab35a0bdf317a8ade9a9e4d9d0bd5"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cf7837c3b11a2dfb589f8530b3cff2bd0307ace4c301e8997e95c7468c1378e"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c62b6fa2961a1dcc51ebe88771be5319a93fd89bd247c9ddf732bc250507bc2b"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb3922a7a804755bbe6b5be9b312e746137a03600f488290318936c1a2d4dc"}, - {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1234dc92d011d3554d929b6cf058ac4a24d188d97be5e04355f1b9223e98bbe9"}, - {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:06ad5543217e0e46fd7ab7ea45d506c76f878b87b1b4e369006bdb01acc05a83"}, - {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fd72fab7bddce46c6826994ce1e7de145ae1e9e106ebb8eb9ce1393ca01444d"}, - {file = "orjson-3.9.10-cp310-none-win32.whl", hash = "sha256:b5b7d4a44cc0e6ff98da5d56cde794385bdd212a86563ac321ca64d7f80c80d1"}, - {file = "orjson-3.9.10-cp310-none-win_amd64.whl", hash = "sha256:61804231099214e2f84998316f3238c4c2c4aaec302df12b21a64d72e2a135c7"}, - {file = "orjson-3.9.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cff7570d492bcf4b64cc862a6e2fb77edd5e5748ad715f487628f102815165e9"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8bc367f725dfc5cabeed1ae079d00369900231fbb5a5280cf0736c30e2adf7"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c812312847867b6335cfb264772f2a7e85b3b502d3a6b0586aa35e1858528ab1"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edd2856611e5050004f4722922b7b1cd6268da34102667bd49d2a2b18bafb81"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:674eb520f02422546c40401f4efaf8207b5e29e420c17051cddf6c02783ff5ca"}, - {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0dc4310da8b5f6415949bd5ef937e60aeb0eb6b16f95041b5e43e6200821fb"}, - {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99c625b8c95d7741fe057585176b1b8783d46ed4b8932cf98ee145c4facf499"}, - {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec6f18f96b47299c11203edfbdc34e1b69085070d9a3d1f302810cc23ad36bf3"}, - {file = "orjson-3.9.10-cp311-none-win32.whl", hash = "sha256:ce0a29c28dfb8eccd0f16219360530bc3cfdf6bf70ca384dacd36e6c650ef8e8"}, - {file = "orjson-3.9.10-cp311-none-win_amd64.whl", hash = "sha256:cf80b550092cc480a0cbd0750e8189247ff45457e5a023305f7ef1bcec811616"}, - {file = "orjson-3.9.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:602a8001bdf60e1a7d544be29c82560a7b49319a0b31d62586548835bbe2c862"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f295efcd47b6124b01255d1491f9e46f17ef40d3d7eabf7364099e463fb45f0f"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92af0d00091e744587221e79f68d617b432425a7e59328ca4c496f774a356071"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5a02360e73e7208a872bf65a7554c9f15df5fe063dc047f79738998b0506a14"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858379cbb08d84fe7583231077d9a36a1a20eb72f8c9076a45df8b083724ad1d"}, - {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666c6fdcaac1f13eb982b649e1c311c08d7097cbda24f32612dae43648d8db8d"}, - {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3fb205ab52a2e30354640780ce4587157a9563a68c9beaf52153e1cea9aa0921"}, - {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7ec960b1b942ee3c69323b8721df2a3ce28ff40e7ca47873ae35bfafeb4555ca"}, - {file = "orjson-3.9.10-cp312-none-win_amd64.whl", hash = "sha256:3e892621434392199efb54e69edfff9f699f6cc36dd9553c5bf796058b14b20d"}, - {file = "orjson-3.9.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8b9ba0ccd5a7f4219e67fbbe25e6b4a46ceef783c42af7dbc1da548eb28b6531"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e2ecd1d349e62e3960695214f40939bbfdcaeaaa62ccc638f8e651cf0970e5f"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f433be3b3f4c66016d5a20e5b4444ef833a1f802ced13a2d852c637f69729c1"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4689270c35d4bb3102e103ac43c3f0b76b169760aff8bcf2d401a3e0e58cdb7f"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd176f528a8151a6efc5359b853ba3cc0e82d4cd1fab9c1300c5d957dc8f48c"}, - {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a2ce5ea4f71681623f04e2b7dadede3c7435dfb5e5e2d1d0ec25b35530e277b"}, - {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:49f8ad582da6e8d2cf663c4ba5bf9f83cc052570a3a767487fec6af839b0e777"}, - {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a11b4b1a8415f105d989876a19b173f6cdc89ca13855ccc67c18efbd7cbd1f8"}, - {file = "orjson-3.9.10-cp38-none-win32.whl", hash = "sha256:a353bf1f565ed27ba71a419b2cd3db9d6151da426b61b289b6ba1422a702e643"}, - {file = "orjson-3.9.10-cp38-none-win_amd64.whl", hash = "sha256:e28a50b5be854e18d54f75ef1bb13e1abf4bc650ab9d635e4258c58e71eb6ad5"}, - {file = "orjson-3.9.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ee5926746232f627a3be1cc175b2cfad24d0170d520361f4ce3fa2fd83f09e1d"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a73160e823151f33cdc05fe2cea557c5ef12fdf276ce29bb4f1c571c8368a60"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c338ed69ad0b8f8f8920c13f529889fe0771abbb46550013e3c3d01e5174deef"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5869e8e130e99687d9e4be835116c4ebd83ca92e52e55810962446d841aba8de"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2c1e559d96a7f94a4f581e2a32d6d610df5840881a8cba8f25e446f4d792df3"}, - {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a3a3a72c9811b56adf8bcc829b010163bb2fc308877e50e9910c9357e78521"}, - {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f8fb7f5ecf4f6355683ac6881fd64b5bb2b8a60e3ccde6ff799e48791d8f864"}, - {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c943b35ecdf7123b2d81d225397efddf0bce2e81db2f3ae633ead38e85cd5ade"}, - {file = "orjson-3.9.10-cp39-none-win32.whl", hash = "sha256:fb0b361d73f6b8eeceba47cd37070b5e6c9de5beaeaa63a1cb35c7e1a73ef088"}, - {file = "orjson-3.9.10-cp39-none-win_amd64.whl", hash = "sha256:b90f340cb6397ec7a854157fac03f0c82b744abdd1c0941a024c3c29d1340aff"}, - {file = "orjson-3.9.10.tar.gz", hash = "sha256:9ebbdbd6a046c304b1845e96fbcc5559cd296b4dfd3ad2509e33c4d9ce07d6a1"}, + {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, + {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, + {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, + {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, + {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, + {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, + {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, + {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, + {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, + {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, + {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, + {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, + {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, + {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, + {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, + {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, + {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, + {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, + {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, + {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, + {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, ] [[package]] @@ -499,41 +451,31 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] - [[package]] name = "platformdirs" -version = "2.5.1" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, - {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -553,13 +495,13 @@ files = [ [[package]] name = "pre-commit" -version = "3.6.0" +version = "4.0.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, + {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, ] [package.dependencies] @@ -570,27 +512,137 @@ pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] -name = "pycodestyle" -version = "2.11.1" -description = "Python style guide checker" +name = "pydantic" +version = "2.10.3" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.1" +typing-extensions = ">=4.12.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + [[package]] -name = "pyflakes" -version = "3.2.0" -description = "passive checker of Python programs" +name = "pydantic-core" +version = "2.27.1" +description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, + {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, + {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, ] +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pygments" version = "2.11.2" @@ -602,27 +654,64 @@ files = [ {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] +[[package]] +name = "pyright" +version = "1.1.390" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.390-py3-none-any.whl", hash = "sha256:ecebfba5b6b50af7c1a44c2ba144ba2ab542c227eb49bc1f16984ff714e0e110"}, + {file = "pyright-1.1.390.tar.gz", hash = "sha256:aad7f160c49e0fbf8209507a15e17b781f63a86a1facb69ca877c71ef2e9538d"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" +nodejs-wheel-binaries = {version = "*", optional = true, markers = "extra == \"nodejs\""} +typing-extensions = ">=4.1" + +[package.extras] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] + [[package]] name = "pytest" -version = "7.4.4" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.25.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, + {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-clarity" @@ -639,19 +728,37 @@ pprintpp = ">=0.4.0" pytest = ">=3.5.0" rich = ">=8.0.0" +[[package]] +name = "pytest-cov" +version = "6.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "pytest-mock" -version = "3.12.0" +version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, - {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, ] [package.dependencies] -pytest = ">=5.0" +pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] @@ -730,23 +837,6 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -optional = false -python-versions = "*" -files = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - [[package]] name = "rich" version = "12.0.0" @@ -765,15 +855,42 @@ pygments = ">=2.6.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] +[[package]] +name = "ruff" +version = "0.8.3" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, + {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, + {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, + {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, + {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, + {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, + {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, +] + [[package]] name = "shortuuid" -version = "1.0.11" +version = "1.0.13" description = "A generator library for concise, unambiguous and URL-safe UUIDs." optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "shortuuid-1.0.11-py3-none-any.whl", hash = "sha256:27ea8f28b1bd0bf8f15057a3ece57275d2059d2b0bb02854f02189962c13b6aa"}, - {file = "shortuuid-1.0.11.tar.gz", hash = "sha256:fc75f2615914815a8e4cb1501b3a513745cb66ef0fd5fc6fb9f8c3fa3481f789"}, + {file = "shortuuid-1.0.13-py3-none-any.whl", hash = "sha256:a482a497300b49b4953e15108a7913244e1bb0d41f9d332f5e9925dba33a3c5a"}, + {file = "shortuuid-1.0.13.tar.gz", hash = "sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72"}, ] [[package]] @@ -800,65 +917,53 @@ files = [ [[package]] name = "structlog" -version = "24.1.0" +version = "24.4.0" description = "Structured Logging for Python" optional = false python-versions = ">=3.8" files = [ - {file = "structlog-24.1.0-py3-none-any.whl", hash = "sha256:3f6efe7d25fab6e86f277713c218044669906537bb717c1807a09d46bca0714d"}, - {file = "structlog-24.1.0.tar.gz", hash = "sha256:41a09886e4d55df25bdcb9b5c9674bccfab723ff43e0a86a1b7b236be8e57b16"}, + {file = "structlog-24.4.0-py3-none-any.whl", hash = "sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610"}, + {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, ] [package.extras] -dev = ["structlog[tests,typing]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] +dev = ["freezegun (>=0.2.8)", "mypy (>=1.4)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "rich", "simplejson", "twisted"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] typing = ["mypy (>=1.4)", "rich", "twisted"] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "virtualenv" -version = "20.13.3" +version = "20.28.0" description = "Virtual Python Environment builder" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.13.3-py2.py3-none-any.whl", hash = "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021"}, - {file = "virtualenv-20.13.3.tar.gz", hash = "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134"}, + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] [package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -platformdirs = ">=2,<3" -six = ">=1.9.0,<2" +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "a99d4a3fbdf84f83e9c5cce534962a9c5e7ce2e8e705fa94d2e1aef43862fc4a" +python-versions = "^3.12" +content-hash = "e532e083808c608b5e14f98b392351cc07b7040bb7e6b5f71e94dd28c8ce7f7f" diff --git a/pyproject.toml b/pyproject.toml index 8dbd12e..e88900f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,24 +5,57 @@ description = "Client for Things Cloud API" authors = ["disrupted "] [tool.poetry.dependencies] -python = "^3.10" -orjson = "^3.9.10" -shortuuid = "^1.0.11" -httpx = "^0.23.3" -structlog = "^24.1.0" -attrs = "^23.2.0" -cattrs = "^23.1.2" +python = "^3.12" +orjson = "^3.10.12" +shortuuid = "^1.0.13" +httpx = "^0.28.1" +structlog = "^24.4.0" +attrs = "^24.2.0" +cattrs = "^24.1.2" +pydantic = "^2.10.3" -[tool.poetry.dev-dependencies] -pytest = "^7.4.4" -pytest-mock = "^3.12.0" +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.4" +pytest-mock = "^3.14.0" +pytest-asyncio = "^0.25.0" +pytest-cov = "^6.0.0" pytest-clarity = "^1.0.1" -freezegun = "^1.2.2" -pre-commit = "^3.6.0" -flake8 = "^7.0.0" -black = "^23.12.1" -isort = "^5.12.0" -mypy = "^1.8" +freezegun = "^1.5.1" +pre-commit = "^4.0.1" +virtualenv = "^20.28.0" # HACK: fix pre-commit: ModuleNotFoundError: No module named 'distutils' +ruff = "^0.8.3" +pyright = { extras = ["nodejs"], version = "^1.1.390" } + +[tool.ruff] +output-format = "grouped" +show-fixes = true + +[tool.ruff.lint] +# For a list of all possible rules visit https://docs.astral.sh/ruff/rules/ +select = [ + "F", # Pyflakes + "E", # pycodestyle Errors + "W", # pycodestyle Warnings + "C90", # mccabe + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "RUF", # Ruff-specific rules +] +ignore = [ + # Rules in conflict with `ruff-format` -- START + "W191", # Checks for indentation that uses tabs. Spaces are preferred. + # "E111", # Checks for indentation with a non-multiple of 4 spaces, add when out of nursery + # "E114", # Checks for indentation of comments with a non-multiple of 4 spaces, add when out of nursery + # "E117", # Checks for over-indented code, add when out of nursery + "E501", # Line too long + # Rules in conflict with `ruff-format` -- END + "RUF012", # type class attrs with `ClassVar` -- Too strict/trigger-happy +] + +[tool.pytest.ini_options] +testpaths = "tests" +addopts = "--cov=things_cloud --cov-report term-missing" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 517a260..0000000 --- a/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -[isort] -profile = black - -[flake8] -ignore = - # E501: line too long - E501, - # W503: line break before binary operator - W503, -max-complexity = 10 -exclude = - .git, - __pycache__ - -[mypy] -python_version = 3.10 -show_error_codes = True -ignore_missing_imports = True -# disable_error_code = name-defined -# warn_return_any = True -warn_unused_configs = True diff --git a/tests/test_things.py b/tests/test_things.py index 01e8be2..cd0dcab 100644 --- a/tests/test_things.py +++ b/tests/test_things.py @@ -1,9 +1,18 @@ from datetime import datetime, timezone +from typing import Any import pytest -from things_cloud.api.client import ThingsClient -from things_cloud.models.todo import Destination, Note, Status, TodoItem, Type +from things_cloud.api.client import HistoryResponse, ThingsClient +from things_cloud.models.todo import ( + Destination, + EditBody, + NewBody, + Note, + Status, + TodoItem, + Type, +) ACCOUNT = "" # TODO OFFSET = 123 @@ -14,15 +23,15 @@ def test_create(): start_idx = things.offset assert start_idx == OFFSET - item = TodoItem("test_create") - new_idx = things.create(item) + item = TodoItem(title="test_create") + new_idx = things.commit(item) assert new_idx is not None assert new_idx == start_idx + 1 -def test_process_new(): - things._items.clear() - data = { +@pytest.fixture() +def history_data_new() -> dict[str, Any]: + return { "items": [ { "aBCDiHyah4Uf0MQqp11jsX": { @@ -68,63 +77,76 @@ def test_process_new(): ], "current-item-index": 1234, "schema": 301, - "start-total-content-size": 0, + "start-total-content-size": 1, # fake "end-total-content-size": 1234567, "latest-total-content-size": 1234567, } - things._process_updates(data) + +@pytest.fixture() +def history_new(history_data_new: dict[str, Any]) -> HistoryResponse: + return HistoryResponse.model_validate(history_data_new) + + +def test_deserialize_history_new(history_new: HistoryResponse): + assert len(history_new.items) == 1 + updates = list(history_new.updates) + assert len(updates) == 1 + assert all(isinstance(update.body, NewBody) for update in updates) + + +def test_process_new(history_new: HistoryResponse): + things._items.clear() + + things._process_updates(history_new) todos = list(things._items.values()) assert len(todos) == 1 time = datetime(2022, 1, 3, 18, 29, 27, tzinfo=timezone.utc) todo = todos[0] - assert todo._uuid == "aBCDiHyah4Uf0MQqp11jsX" - assert todo._index == 1234 - assert todo._title == "test task" - assert todo._status == Status.TODO - assert todo._destination == Destination.ANYTIME - assert todo._creation_date == time - assert todo._modification_date == time - assert todo._scheduled_date is None - assert todo._today_index_reference_date is None - assert todo._completion_date is None - assert todo._due_date is None - assert todo._trashed is False - assert todo._instance_creation_paused is False + assert todo.uuid == "aBCDiHyah4Uf0MQqp11jsX" + assert todo.index == 1234 + assert todo.title == "test task" + assert todo.status is Status.TODO + assert todo.destination is Destination.ANYTIME + assert todo.creation_date == time + assert todo.modification_date == time + assert todo.scheduled_date is None + assert todo.today_index_reference_date is None + assert todo.completion_date is None + assert todo.due_date is None + assert todo.trashed is False + assert todo.instance_creation_paused is False assert todo._projects == ["ABCd1ee0ykmXYZqT98huxa"] assert todo._areas == [] - assert todo._is_evening is False - assert todo._tags == [] + assert todo._evening is False + assert todo.is_evening is False + assert todo.tags == [] assert todo._type == Type.TASK - assert todo._due_date_suppression_date is None - assert todo._repeating_template == [] - assert todo._repeater_migration_date is None - assert todo._delegate == [] - assert todo._due_date_offset == 0 - assert todo._last_alarm_interaction_date is None - assert todo._action_group == [] - assert todo._leaves_tombstone is False - assert todo._instance_creation_count == 0 - assert todo._today_index == 0 - assert todo._reminder is None - assert todo._instance_creation_start_date is None - assert todo._repeater is None - assert todo._after_completion_reference_date is None - assert todo._recurrence_rule is None - assert todo._note == Note() - assert not todo._changes - - -def test_process_updated(): - things._items.clear() - UUID = "aBCDiHyah4Uf0MQqp11jsX" - todo = TodoItem("test original") - todo._uuid = UUID - things._items = {UUID: todo} - data = { + assert todo.due_date_suppression_date is None + assert todo.repeating_template == [] + assert todo.repeater_migration_date is None + assert todo.delegate == [] + assert todo.due_date_offset == 0 + assert todo.last_alarm_interaction_date is None + assert todo.action_group == [] + assert todo.leaves_tombstone is False + assert todo.instance_creation_count == 0 + assert todo.today_index == 0 + assert todo.reminder is None + assert todo.instance_creation_start_date is None + assert todo.repeater is None + assert todo.after_completion_reference_date is None + assert todo.recurrence_rule is None + assert todo.note == Note() + # assert not todo._changes + + +@pytest.fixture() +def history_data_edit() -> dict[str, Any]: + return { "items": [ { - UUID: { + "aBCDiHyah4Uf0MQqp11jsX": { "p": {"md": 1641234567.123456, "tt": "test updated"}, "e": "Task6", "t": 1, @@ -133,41 +155,58 @@ def test_process_updated(): ], "current-item-index": 1234, "schema": 301, - "start-total-content-size": 0, + "start-total-content-size": 1, "end-total-content-size": 1234567, "latest-total-content-size": 1234567, } - things._process_updates(data) - todos = things._items - assert len(todos) == 1 - todo = todos[UUID] - assert todo._uuid == UUID - assert todo._title == "test updated" - assert todo._modification_date == datetime( + +@pytest.fixture() +def history_edit(history_data_edit: dict[str, Any]) -> HistoryResponse: + return HistoryResponse.model_validate(history_data_edit) + + +def test_deserialize_history_edit(history_edit: HistoryResponse): + assert len(history_edit.items) == 1 + updates = list(history_edit.updates) + assert len(updates) == 1 + assert all(isinstance(update.body, EditBody) for update in updates) + + +def test_process_updated(history_edit: HistoryResponse): + things._items.clear() + update = next(history_edit.updates) + + todo = TodoItem(title="test original") + todo._uuid = update.id + things._items[update.id] = todo + + things._process_updates(history_edit) + assert len(things._items) == 1 + updated_todo = things._items[todo.uuid] + assert updated_todo.uuid == todo.uuid + assert updated_todo.title == "test updated" + assert updated_todo.modification_date == datetime( 2022, 1, 3, 18, 29, 27, 123456, tzinfo=timezone.utc ) - assert not todo._changes + # assert not todo._changes -def test_process_multiple(): - things._items.clear() - UUID1 = "aBCDiHyah4Uf0MQqp11js1" - UUID2 = "aBCDiHyah4Uf0MQqp11js2" - UUID3 = "aBCDiHyah4Uf0MQqp11js3" - data = { +@pytest.fixture() +def history_data_mixed() -> dict[str, Any]: + return { "items": [ # updated non-existant todo - { - UUID1: { - "p": {"md": 1641234567.123456, "tt": "test updated"}, - "e": "Task6", - "t": 1, - } - }, + # { + # "aBCDiHyah4Uf0MQqp11js1": { + # "p": {"md": 1641234567.123456, "tt": "test updated"}, + # "e": "Task6", + # "t": 1, + # } + # }, # new todo { - UUID2: { + "aBCDiHyah4Uf0MQqp11js2": { "p": { "ix": 2, "cd": 1641234567, @@ -209,7 +248,7 @@ def test_process_multiple(): }, # new todo { - UUID3: { + "aBCDiHyah4Uf0MQqp11js3": { "p": { "ix": 3, "cd": 1641234567, @@ -252,17 +291,29 @@ def test_process_multiple(): ], "current-item-index": 1234, "schema": 301, - "start-total-content-size": 0, + "start-total-content-size": 1, "end-total-content-size": 1234567, "latest-total-content-size": 1234567, } - things._process_updates(data) + +@pytest.fixture() +def history_mixed(history_data_mixed: dict[str, Any]) -> HistoryResponse: + return HistoryResponse.model_validate(history_data_mixed) + + +def test_process_multiple(history_mixed: HistoryResponse): + things._items.clear() + + things._process_updates(history_mixed) todos = things._items + UUID2 = "aBCDiHyah4Uf0MQqp11js2" + UUID3 = "aBCDiHyah4Uf0MQqp11js3" + assert len(todos) == 2 todo2 = todos[UUID2] - assert todo2._uuid == UUID2 - assert todo2._title == "task 2" + assert todo2.uuid == UUID2 + assert todo2.title == "task 2" todo3 = todos[UUID3] - assert todo3._uuid == UUID3 - assert todo3._title == "task 3" + assert todo3.uuid == UUID3 + assert todo3.title == "task 3" diff --git a/tests/test_todo.py b/tests/test_todo.py index 7f43605..b666ce0 100644 --- a/tests/test_todo.py +++ b/tests/test_todo.py @@ -1,17 +1,14 @@ from datetime import datetime, timezone import pytest -from attrs import asdict -from things_cloud.models.serde import TodoSerde from things_cloud.models.todo import ( Destination, Note, Status, + TodoApiObject, TodoItem, Type, - deserialize, - serialize_dict, ) from things_cloud.utils import Util @@ -19,7 +16,7 @@ @pytest.fixture(autouse=True) -def todo(monkeypatch): +def mock_time(monkeypatch): monkeypatch.setattr("things_cloud.utils.Util.now", lambda: FAKE_TIME) @@ -27,290 +24,271 @@ def test_mocked_now(): assert Util.now() == FAKE_TIME -@pytest.mark.skip("fix Util.now mock") -def test_todo_schema_create(): - item = TodoItem("test") +@pytest.fixture() +def task() -> TodoItem: + return TodoItem(title="test task") - d = { - "index": 0, - "title": "test", - "status": Status.TODO, - "destination": Destination.INBOX, - "creation_date": FAKE_TIME, - "modification_date": FAKE_TIME, - "scheduled_date": None, - "completion_date": None, - "acrd": None, - "agr": [], - "areas": [], - "reminder": None, - "due_date": None, - "dds": None, - "dl": [], - "do": 0, - "icc": 0, - "instance_creation_paused": False, - "icsd": None, - "lai": None, - "lt": False, - "note": {"_t": "tx", "ch": 0, "t": 0, "v": ""}, - "projects": [], - "rmd": None, - "rp": None, - "rr": None, - "rt": [], - "is_evening": 0, - "tags": [], - "ti": 0, - "tir": None, - "tp": 0, - "in_trash": False, - } - timestamp = TodoSerde.timestamp_rounded(FAKE_TIME) - d_alias = { - "ix": 0, - "tt": "test", - "st": 0, - "cd": timestamp, - "md": FAKE_TIME, - "sr": None, - "ss": 0, - "acrd": None, - "agr": [], - "ar": [], - "ato": None, - "dd": None, - "dds": None, - "dl": [], - "do": 0, - "icc": 0, - "icp": False, - "icsd": None, - "lai": None, - "lt": False, - "nt": {"_t": "tx", "ch": 0, "t": 0, "v": ""}, - "pr": [], - "rmd": None, - "rp": None, - "rr": None, - "rt": [], - "sb": 0, - "sp": None, - "tg": [], - "ti": 0, - "tir": None, - "tp": 0, - "tr": False, - } - assert asdict(item) == d - assert serialize_dict(item) == d_alias +@pytest.fixture() +def project() -> TodoItem: + return TodoItem(title="test project").as_project() -def test_as_project(): - project = TodoItem().as_project() - assert project._type == Type.PROJECT - assert project._instance_creation_paused is True - assert project.destination == Destination.ANYTIME - assert project.changes == { - "_destination", - "_instance_creation_paused", - "_type", - "_modification_date", - } +def test_basic(task: TodoItem): + assert task.type is Type.TASK -def test_assign_project_uuid(): - todo = TodoItem("test task") - todo.project = "test-project" - assert todo._projects == ["test-project"] - assert todo.project == "test-project" - assert todo._areas == [] - assert todo.area is None - assert todo.destination == Destination.ANYTIME - assert todo.changes == { - "_destination", - "_projects", - "_modification_date", - } +# def test_reset_changes(todo: TodoItem): +# assert todo.changes == {"title"} +# todo.reset_changes() +# assert not todo.changes -def test_assign_project_item(): - project = TodoItem("test project").as_project() - todo = TodoItem("test task") - todo.project = project - assert todo._projects == [project.uuid] - assert todo.project == project.uuid - assert isinstance(todo.project, str) - assert todo._areas == [] - assert todo.area is None - assert todo.destination == Destination.ANYTIME - assert todo.changes == { - "_destination", - "_projects", - "_modification_date", - } +def test_update_title(task: TodoItem): + task.title = "updated task" + # assert todo.changes == {"title", "_modification_date"} -def test_assign_project_invalid(): - not_project = TodoItem("not project") - todo = TodoItem("test task") - with pytest.raises(ValueError): - todo.project = not_project - assert not todo.project - assert not todo.area - assert not todo.destination - assert not todo.changes +def test_as_project(project: TodoItem): + assert project.type is Type.PROJECT + assert project.instance_creation_paused is True + assert project.destination is Destination.ANYTIME + # assert project.changes == { + # "title", + # "_destination", + # "_instance_creation_paused", + # "type", + # "_modification_date", + # } -def test_clear_project(): - todo = TodoItem("test task") - todo._projects = ["test-project"] - assert not todo.changes +def test_assign_project_uuid(task: TodoItem, project: TodoItem): + task.project = project.uuid + assert task._projects == [project.uuid] + assert task.project == project.uuid + assert task._areas == [] + assert task.area is None + assert task.destination is Destination.ANYTIME + # assert todo.changes == { + # "title", + # "_destination", + # "projects_", + # "_modification_date", + # } - # clear project - todo.project = None - assert not todo.project - assert not todo.area - assert not todo.destination - assert todo.changes == { - "_projects", - "_modification_date", - } + +def test_assign_project_uuid_self(project: TodoItem): + with pytest.raises(ValueError): + project.project = project.uuid + # assert not project.changes + + +def test_assign_project_item(task: TodoItem, project: TodoItem): + task.project = project + assert task._projects == [project.uuid] + assert task.project == project.uuid + assert isinstance(task.project, str) + assert task._areas == [] + assert task.area is None + assert task.destination is Destination.ANYTIME + # assert todo.changes == { + # "title", + # "_destination", + # "projects_", + # "_modification_date", + # } + + +def test_assign_project_self(project: TodoItem): + with pytest.raises(ValueError): + project.project = project + # assert not project.changes -def test_assign_area_uuid(): - todo = TodoItem("test task") - todo.area = "test-area" - assert todo._areas == ["test-area"] - assert todo.area == "test-area" - assert todo._projects == [] - assert todo.project is None - assert todo.destination == Destination.ANYTIME - assert todo.changes == { - "_destination", - "_areas", - "_modification_date", - } +def test_assign_project_invalid(task: TodoItem): + not_project = TodoItem(title="not project") + with pytest.raises(ValueError): + task.project = not_project + assert not task.project + assert not task.area + assert not task.destination + # assert not todo.changes -def test_clear_area(): - todo = TodoItem("test task") - todo._areas = ["test-area"] - assert not todo.changes +def test_clear_project(task: TodoItem): + task._projects = ["test-project"] + # assert not todo.changes + + # clear project + task.project = None + assert not task.project + assert not task.area + assert not task.destination + # assert todo.changes == { + # "projects_", + # "_modification_date", + # } + + +def test_assign_area_uuid(task: TodoItem): + task.area = "test-area" + assert task._areas == ["test-area"] + assert task.area == "test-area" + assert task._projects == [] + assert task.project is None + assert task.destination is Destination.ANYTIME + # assert task.changes == { + # "_destination", + # "areas_", + # "_modification_date", + # } + + +def test_clear_area(task: TodoItem): + task._areas = ["test-area"] # clear area - todo.area = None - assert not todo.area - assert not todo.project - assert not todo.destination - assert todo.changes == { - "_areas", - "_modification_date", - } + task.area = None + assert not task.area + assert not task.project + assert not task.destination + # assert task.changes == { + # "areas_", + # "_modification_date", + # } -def test_todo(): - todo = TodoItem("test task") +def test_todo(task: TodoItem): # should fail if status is already todo with pytest.raises(ValueError): - todo.todo() - assert not todo.changes - - todo._status = Status.COMPLETE - todo._completion_date = datetime(2022, 1, 1) - todo.todo() - assert todo._status == Status.TODO - assert todo._completion_date is None - assert todo.changes == { - "_status", - "_completion_date", - "_modification_date", - } - - -def test_complete(): - todo = TodoItem("test task") - todo._status = Status.COMPLETE - todo._completion_date = datetime(2022, 1, 1) + task.todo() + + task.status = Status.COMPLETE + task.completion_date = datetime(2022, 1, 1) + task.todo() + assert task.status is Status.TODO + assert task.completion_date is None + # assert task.changes == { + # "_status", + # "_completion_date", + # "_modification_date", + # } + + +def test_complete(task: TodoItem): + assert task.status is Status.TODO + assert task.completion_date is None + task.complete() + assert task.status is Status.COMPLETE + assert task.completion_date == Util.now() + # assert task.changes == { + # "_status", + # "_completion_date", + # "_modification_date", + # } + + +def test_complete_already_completed(task: TodoItem): + task.status = Status.COMPLETE + task.completion_date = datetime(2022, 1, 1) # should fail if status is already complete with pytest.raises(ValueError): - todo.complete() - assert not todo.changes - - todo._status = Status.TODO - todo._completion_date = None - todo.complete() - assert todo._status == Status.COMPLETE - assert todo._completion_date == Util.now() - assert todo.changes == { - "_status", - "_completion_date", - "_modification_date", - } + task.complete() + # assert not task.changes -def test_cancel(): - todo = TodoItem("test task") - todo._status = Status.CANCELLED - todo._completion_date = datetime(2022, 1, 1) +def test_cancel(task: TodoItem): + task.cancel() + assert task.status is Status.CANCELLED + assert task.completion_date == Util.now() + # assert task.changes == { + # "_status", + # "_completion_date", + # "_modification_date", + # } + + +def test_cancel_already_cancelled(task: TodoItem): + task.status = Status.CANCELLED + task.completion_date = datetime(2022, 1, 1) # should fail if status is already cancelled with pytest.raises(ValueError): - todo.cancel() - assert not todo.changes - - todo._status = Status.TODO - todo._completion_date = None - todo.cancel() - assert todo._status == Status.CANCELLED - assert todo._completion_date == Util.now() - assert todo.changes == { - "_status", - "_completion_date", - "_modification_date", - } + task.cancel() + # assert not task.changes -def test_today(): - todo = TodoItem("test task") - assert todo.is_today is False - assert todo.is_evening is False - todo.today() - assert todo.is_today is True - assert todo.is_evening is False - assert todo.destination == Destination.ANYTIME - assert todo.scheduled_date == Util.today() - assert todo._is_evening is False - assert todo._today_index_reference_date == Util.today() - assert todo.changes == { - "_destination", - "_scheduled_date", - "_today_index_reference_date", - "_modification_date", - } +def test_delete(task: TodoItem): + assert task.trashed is False + task.delete() + assert task.trashed is True + # assert task.changes == { + # "_modification_date", + # "trashed", + # } -def test_evening(): - todo = TodoItem("test task") - assert todo.is_today is False - assert todo.is_evening is False - todo.evening() - assert todo.is_today is True - assert todo.is_evening is True - assert todo.destination == Destination.ANYTIME - assert todo.scheduled_date == Util.today() - assert todo._today_index_reference_date == Util.today() - assert todo._is_evening is True - assert todo.changes == { - "_destination", - "_scheduled_date", - "_today_index_reference_date", - "_is_evening", - "_modification_date", - } +def test_delete_already_trashed(task: TodoItem): + task.trashed = True + with pytest.raises(ValueError): + task.delete() + # assert not task.changes + +def test_restore(task: TodoItem): + task.delete() + assert task.trashed is True + task.restore() + assert task.trashed is False + # assert task.changes == { + # "_modification_date", + # "trashed", + # } -def test_deserialize(): + +def test_restore_not_trashed(task: TodoItem): + with pytest.raises(ValueError): + task.restore() + # assert not task.changes + + +def test_today(task: TodoItem): + assert task.is_today is False + assert task.is_evening is False + task.today() + assert task.is_today is True + assert task.is_evening is False + assert task.destination is Destination.ANYTIME + assert task.scheduled_date == Util.today() + assert task._evening is False + # assert task.today_index_reference_date == Util.today() # FIXME + # assert task.changes == { + # "_destination", + # "scheduled_date_", + # "today_index_reference_date_", + # "_modification_date", + # } + + +def test_evening(task: TodoItem): + assert task.is_today is False + assert task.is_evening is False + task.evening() + assert task.is_today is True + assert task.is_evening is True + assert task.destination is Destination.ANYTIME + assert task.scheduled_date == Util.today() + assert task._evening is True + # assert task.today_index_reference_date == Util.today() # FIXME + # assert task.changes == { + # "_destination", + # "scheduled_date_", + # "today_index_reference_date_", + # "evening_", + # "_modification_date", + # } + + +def test_serde(): api_object = { "ix": 1234, "cd": 1641234567, @@ -346,48 +324,178 @@ def test_deserialize(): "sb": 0, "agr": [], } - todo = deserialize(api_object) + todo = TodoApiObject.model_validate(api_object) time = datetime(2022, 1, 3, 18, 29, 27, tzinfo=timezone.utc) - assert todo._index == 1234 - assert todo._title == "test task" - assert todo._status == Status.TODO - assert todo._destination == Destination.ANYTIME - assert todo._creation_date == time - assert todo._modification_date == time - assert todo._scheduled_date is None - assert todo._today_index_reference_date is None - assert todo._completion_date is None - assert todo._due_date is None - assert todo._trashed is False - assert todo._instance_creation_paused is False + assert todo.index == 1234 + assert todo.title == "test task" + assert todo.status is Status.TODO + assert todo.destination is Destination.ANYTIME + assert todo.creation_date == time + assert todo.modification_date == time + assert todo.scheduled_date is None + assert todo.today_index_reference_date is None + assert todo.completion_date is None + assert todo.due_date is None + assert todo.trashed is False + assert todo.instance_creation_paused is False + assert todo.projects == ["ABCd1ee0ykmXYZqT98huxa"] + assert todo.areas == [] + assert todo.evening is False + assert todo.tags == [] + assert todo.type is Type.TASK + assert todo.due_date_suppression_date is None + assert todo.repeating_template == [] + assert todo.repeater_migration_date is None + assert todo.delegate == [] + assert todo.due_date_offset == 0 + assert todo.last_alarm_interaction_date is None + assert todo.action_group == [] + assert todo.leaves_tombstone is False + assert todo.instance_creation_count == 0 + assert todo.today_index == 0 + assert todo.reminder is None + assert todo.instance_creation_start_date is None + assert todo.repeater is None + assert todo.after_completion_reference_date is None + assert todo.recurrence_rule is None + assert todo.note == Note() + # assert not todo._changes + assert todo.model_dump(mode="json", by_alias=True) == api_object + + +def test_todo_from_api_object(): + time = datetime(2022, 1, 3, 18, 29, 27, tzinfo=timezone.utc) + api_object = TodoApiObject.model_validate( + { + "index": 1234, + "title": "test task", + "status": Status.TODO, + "destination": Destination.ANYTIME, + "creation_date": time, + "modification_date": time, + "scheduled_date": None, + "today_index_reference_date": None, + "completion_date": None, + "due_date": None, + "trashed": False, + "instance_creation_paused": False, + "projects": ["ABCd1ee0ykmXYZqT98huxa"], + "areas": [], + "evening": False, + "tags": [], + "type": Type.TASK, + "due_date_suppression_date": None, + "repeating_template": [], + "repeater_migration_date": None, + "delegate": [], + "due_date_offset": 0, + "last_alarm_interaction_date": None, + "action_group": [], + "leaves_tombstone": False, + "instance_creation_count": 0, + "today_index": 0, + "reminder": None, + "instance_creation_start_date": None, + "repeater": None, + "after_completion_reference_date": None, + "recurrence_rule": None, + "note": Note(), + } + ) + todo = api_object.to_todo() + assert todo.index == 1234 + assert todo.title == "test task" + assert todo.status is Status.TODO + assert todo.destination is Destination.ANYTIME + assert todo.creation_date == time + assert todo.modification_date == time + assert todo.scheduled_date is None + assert todo.today_index_reference_date is None + assert todo.completion_date is None + assert todo.due_date is None + assert todo.trashed is False + assert todo.instance_creation_paused is False assert todo._projects == ["ABCd1ee0ykmXYZqT98huxa"] assert todo._areas == [] - assert todo._is_evening is False - assert todo._tags == [] - assert todo._type == Type.TASK - assert todo._due_date_suppression_date is None - assert todo._repeating_template == [] - assert todo._repeater_migration_date is None - assert todo._delegate == [] - assert todo._due_date_offset == 0 - assert todo._last_alarm_interaction_date is None - assert todo._action_group == [] - assert todo._leaves_tombstone is False - assert todo._instance_creation_count == 0 - assert todo._today_index == 0 - assert todo._reminder is None - assert todo._instance_creation_start_date is None - assert todo._repeater is None - assert todo._after_completion_reference_date is None - assert todo._recurrence_rule is None - assert todo._note == Note() - assert not todo._changes - assert serialize_dict(todo) == api_object - - -def test_update(): - todo = TodoItem("original") - update = TodoItem("updated") - keys = {"tt"} - todo.update(update, keys) - assert todo.title == "updated" + assert todo.is_evening is False + assert todo.tags == [] + assert todo.type is Type.TASK + assert todo.due_date_suppression_date is None + assert todo.repeating_template == [] + assert todo.repeater_migration_date is None + assert todo.delegate == [] + assert todo.due_date_offset == 0 + assert todo.last_alarm_interaction_date is None + assert todo.action_group == [] + assert todo.leaves_tombstone is False + assert todo.instance_creation_count == 0 + assert todo.today_index == 0 + assert todo.reminder is None + assert todo.instance_creation_start_date is None + assert todo.repeater is None + assert todo.after_completion_reference_date is None + assert todo.recurrence_rule is None + assert todo.note == Note() + assert todo._api_object == api_object + + +def test_to_new(task: TodoItem): + assert task._api_object is None + new = task._to_new() + # we simulate what happens when we commit the changes + task._commit(new) + assert task._api_object + assert task._api_object.title == "test task" + + +def test_to_new_exists(task: TodoItem): + assert task._api_object is None + new = task._to_new() + # we simulate what happens when we commit the changes + task._commit(new) + + with pytest.raises( + ValueError, match="^current version exists for todo, use _to_edit instead$" + ): + task._to_new() + + +def test_to_edit_does_not_exist(task: TodoItem): + with pytest.raises( + ValueError, match="^no current version exists for todo, use _to_new instead$" + ): + task._to_edit() + + +def test_to_edit_unchanged(task: TodoItem): + assert task._api_object is None + new = task._to_new() + # we simulate what happens when we commit the changes + task._commit(new) + + with pytest.raises(ValueError, match="^no changes found$"): + task._to_edit() + + +def test_to_edit(task: TodoItem): + assert task._api_object is None + new = task._to_new() + # we simulate what happens when we commit the changes + task._commit(new) + + task.title = "updated task" + delta = task._to_edit() + assert delta + assert delta.model_dump(exclude_none=True) == {"title": "updated task"} + + +def test_to_edit_detect_reverts(task: TodoItem): + assert task._api_object is None + new = task._to_new() + # we simulate what happens when we commit the changes + task._commit(new) + + task.title = "updated task" # first we change something + task.title = "test task" # but then we revert to the old value + with pytest.raises(ValueError, match="no changes found"): + task._to_edit() diff --git a/tests/test_utils.py b/tests/test_utils.py index f1cc614..b7fef7b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,7 +9,7 @@ @freeze_time(FAKE_TIME) def test_now(): - assert Util.now() == datetime(2021, 11, 1, 19, 0, 59) + assert Util.now() == datetime(2021, 11, 1, 19, 0, 59, tzinfo=timezone.utc) @freeze_time(FAKE_TIME) diff --git a/things_cloud/api/client.py b/things_cloud/api/client.py index 67737a3..a7e7ea9 100644 --- a/things_cloud/api/client.py +++ b/things_cloud/api/client.py @@ -1,13 +1,17 @@ -from typing import Any - import httpx from httpx import Request, RequestError, Response from structlog import get_logger from things_cloud.api.const import API_BASE, HEADERS -from things_cloud.api.exceptions import ThingsCloudException, ThingsUpdateException -from things_cloud.models.serde import JsonSerde -from things_cloud.models.todo import TodoItem, deserialize, serialize_dict +from things_cloud.api.exceptions import ThingsCloudException +from things_cloud.models.todo import ( + CommitResponse, + HistoryResponse, + NewBody, + TodoItem, + Update, + UpdateType, +) from things_cloud.utils import Util log = get_logger() @@ -56,15 +60,19 @@ def offset(self) -> int: return self._offset def update(self) -> None: - self._offset = self.__fetch(self._offset) - - def create(self, item: TodoItem) -> None: - self.update() - item._index = self._offset + 1 - self.__create_todo(self._offset, item) + data = self.__fetch(self._offset) + self._process_updates(data) + self._offset = data.current_item_index - def edit(self, item: TodoItem) -> None: - self.__modify_todo(self._offset, item) + def commit(self, item: TodoItem) -> None: + update = item.to_update() + try: + commit = self.__commit(update) + item._commit(update.body.payload) + self._offset = commit.server_head_index + except ThingsCloudException as e: + log.error("Error commiting") + raise e def __request(self, method: str, endpoint: str, **kwargs) -> Response: try: @@ -72,7 +80,7 @@ def __request(self, method: str, endpoint: str, **kwargs) -> Response: except RequestError as e: raise ThingsCloudException from e - def __fetch(self, index: int) -> int: + def __fetch(self, index: int) -> HistoryResponse: response = self.__request( "GET", "/items", @@ -81,36 +89,29 @@ def __fetch(self, index: int) -> int: }, ) if response.status_code == 200: - data = response.json() - self._process_updates(data) - return data["current-item-index"] + return HistoryResponse.model_validate_json(response.read()) else: log.error("Error getting current index", response=response) raise ThingsCloudException - def _process_updates(self, data: dict) -> None: - if not data["items"]: - return - - updates: list[dict[str, dict]] = data["items"] - for update in updates: - for uuid, body in update.items(): # actually just one item - log.debug("found update", uuid=uuid, body=body) - item: dict[str, Any] = body["p"] - todo = deserialize(item) - todo._uuid = uuid - if body["t"] == 0: # new todo - self._items[uuid] = todo - elif body["t"] == 1: # edited todo - self._apply_edits(todo, set(item.keys())) - else: - raise ThingsUpdateException - - def _apply_edits(self, update: TodoItem, keys: set[str]) -> None: - try: - self._items[update.uuid].update(update, keys) - except KeyError: - log.error(f"todo {update.uuid} not found") + def _process_updates(self, history: HistoryResponse) -> None: + for update in history.updates: + log.debug("processing update", update=update) + match update.body.type: + case UpdateType.NEW: + assert isinstance( + update.body, NewBody + ) # HACK: type narrowing does not work + item = update.body.payload.to_todo() + item._uuid = update.id + self._items[item.uuid] = item + case UpdateType.EDIT: + try: + item = self._items[update.id] + except KeyError as key_err: + msg = f"todo {id} not found" + raise ValueError(msg) from key_err + update.body.payload.apply_edits(item) # HACK: temporary def today(self) -> list[TodoItem]: @@ -120,11 +121,10 @@ def today(self) -> list[TodoItem]: if item.scheduled_date == Util.today() ] - def __commit( - self, - index: int, - data: dict | None = None, - ) -> int: + def __commit(self, update: Update) -> CommitResponse: + index = self._offset + if update.body.type is UpdateType.NEW: + index += 1 response = self.__request( method="POST", endpoint="/commit", @@ -132,32 +132,6 @@ def __commit( "ancestor-index": str(index), "_cnt": "1", }, - content=JsonSerde.dumps(data), + content=update.to_api_payload(), ) - return response.json()["server-head-index"] - - def __create_todo(self, index: int, item: TodoItem) -> None: - data = {item.uuid: {"t": 0, "e": "Task6", "p": serialize_dict(item)}} - log.debug("", data=data) - - try: - self._offset = self.__commit(index, data) - item.reset_changes() - except ThingsCloudException as e: - log.error("Error creating todo") - raise e - - def __modify_todo(self, index: int, item: TodoItem) -> None: - changes = item.changes - if not changes: - log.warning("there are no changes to be sent") - return - data = {item.uuid: {"t": 1, "e": "Task6", "p": serialize_dict(item, changes)}} - log.debug("", data=data) - - try: - self._offset = self.__commit(index, data) - item.reset_changes() - except ThingsCloudException as e: - log.error("Error modifying todo") - raise e + return CommitResponse.model_validate_json(response.read()) diff --git a/things_cloud/models/serde.py b/things_cloud/models/serde.py index 27e3492..7b472c0 100644 --- a/things_cloud/models/serde.py +++ b/things_cloud/models/serde.py @@ -1,6 +1,6 @@ -import datetime as dt from abc import ABC, abstractmethod from collections.abc import Callable +from datetime import datetime, time, timezone from typing import Any import orjson @@ -11,10 +11,10 @@ class Serde(ABC): type_serializers: dict[type, Callable] @abstractmethod - def serialize(self, v, *, default=None) -> str: ... # noqa: E704 + def serialize(self, v, *, default=None) -> str: ... @abstractmethod - def deserialize(self, v) -> Any: ... # noqa: E704 + def deserialize(self, v) -> Any: ... class JsonSerde(Serde): @@ -54,12 +54,18 @@ class TodoSerde(JsonSerde): } type_serializers: dict[type, Callable] = { - dt.time: lambda t: (t.hour * 60 + t.minute) * 60 + t.second, - dt.datetime: lambda d: d.timestamp(), + time: lambda t: (t.hour * 60 + t.minute) * 60 + t.second, + datetime: lambda d: d.timestamp(), } @staticmethod - def timestamp_rounded(d: dt.datetime | None) -> int | None: - if d is None: + def from_timestamp(timestamp: datetime | int) -> datetime: + if isinstance(timestamp, datetime): + return timestamp + return datetime.fromtimestamp(timestamp, timezone.utc) + + @staticmethod + def timestamp_rounded(dt: datetime | None) -> int | None: + if dt is None: return None - return int(d.timestamp()) + return int(dt.timestamp()) diff --git a/things_cloud/models/todo.py b/things_cloud/models/todo.py index 7fa7284..2ea879b 100644 --- a/things_cloud/models/todo.py +++ b/things_cloud/models/todo.py @@ -1,159 +1,424 @@ from __future__ import annotations -from collections.abc import Callable -from datetime import datetime, time, timezone -from enum import Enum -from typing import Any, Deque, ParamSpec, TypeVar +from collections.abc import Iterator +from datetime import datetime, time +from enum import IntEnum, StrEnum +from typing import Annotated, Any, Literal -import cattrs -from attrs import define, field -from cattr.gen import make_dict_structure_fn -from cattrs.gen import make_dict_unstructure_fn, override +import pydantic from things_cloud.models.serde import TodoSerde from things_cloud.utils import Util -SERDE = TodoSerde() +ShortUUID = Annotated[str, pydantic.StringConstraints(min_length=22, max_length=22)] -class Type(int, Enum): +class CommitResponse(pydantic.BaseModel): + server_head_index: Annotated[ + pydantic.PositiveInt, pydantic.Field(alias="server-head-index") + ] + + +class HistoryResponse(pydantic.BaseModel): + current_item_index: Annotated[ + pydantic.PositiveInt, pydantic.Field(alias="current-item-index") + ] + end_total_content_size: Annotated[ + pydantic.PositiveInt, pydantic.Field(alias="end-total-content-size") + ] + latest_total_content_size: Annotated[ + pydantic.PositiveInt, pydantic.Field(alias="latest-total-content-size") + ] + schema_: Annotated[pydantic.PositiveInt, pydantic.Field(alias="schema")] # 301 + start_total_content_size: Annotated[ + pydantic.PositiveInt, pydantic.Field(alias="start-total-content-size") + ] + items: Annotated[list[dict[str, Body]], pydantic.Field(min_length=1)] + + @property + def updates(self) -> Iterator[Update]: + for item in self.items: + assert ( + isinstance(item, dict) and len(item) == 1 + ), "Expected items dict with one key-value pair" + key, value = next(iter(item.items())) + yield Update(id=key, body=value) + + +class Update(pydantic.BaseModel): + id: ShortUUID + body: Body = pydantic.Field(discriminator="type") + + # @pydantic.model_validator(mode="after") + # def inject_task_id(self) -> Self: + # self.body.payload._uuid = self.id + # return self + + def to_api_payload(self) -> dict[ShortUUID, dict[str, Any]]: + return {self.id: self.body.to_api_payload()} + + +class UpdateType(IntEnum): + NEW = 0 + EDIT = 1 + + +class EntityType(StrEnum): + TASK_6 = "Task6" + + +class NewBody(pydantic.BaseModel): + type: Annotated[Literal[UpdateType.NEW], pydantic.Field(alias="t")] = UpdateType.NEW + payload: Annotated[TodoApiObject, pydantic.Field(alias="p")] + entity: Annotated[EntityType, pydantic.Field(alias="e")] = EntityType.TASK_6 + + def to_api_payload(self) -> dict[str, Any]: + return self.model_dump(mode="json", by_alias=True) + + +class EditBody(pydantic.BaseModel): + type: Annotated[Literal[UpdateType.EDIT], pydantic.Field(alias="t")] = ( + UpdateType.EDIT + ) + payload: Annotated[TodoDeltaApiObject, pydantic.Field(alias="p")] + entity: Annotated[EntityType, pydantic.Field(alias="e")] = EntityType.TASK_6 + + def to_api_payload(self) -> dict[str, Any]: + return self.model_dump(mode="json", by_alias=True, exclude_none=True) + + +Body = Annotated[NewBody | EditBody, pydantic.Field(discriminator="type")] + + +class Type(IntEnum): TASK = 0 PROJECT = 1 HEADING = 2 -class Destination(int, Enum): +class Destination(IntEnum): # destination: {0: inbox, 1: anytime/today/evening, 2: someday} INBOX = 0 ANYTIME = 1 SOMEDAY = 2 -class Status(int, Enum): +class Status(IntEnum): TODO = 0 CANCELLED = 2 COMPLETE = 3 -@define -class Note: - _t: str = field(init=False, default="tx") +class Note(pydantic.BaseModel): + t_: str = pydantic.Field(alias="_t", default="tx") ch: int = 0 v: str = "" # value t: int = 0 -P = ParamSpec("P") -_R = TypeVar("_R") - - -def mod(*field_names: str): - def decorate(func: Callable[..., _R]): - def wrapper(self: TodoItem, *args: P.args, **kwargs: P.kwargs) -> _R: - # first we call the wrapped function, - # in case it throws an exception we don't want to modify - ret = func(self, *args, **kwargs) - self._modification_date = Util.now() - self._changes.extend(field_names) - self._changes.append("_modification_date") - return ret - - return wrapper - - return decorate - - -@define -class TodoItem: - _uuid: str = field(factory=Util.uuid, init=False) - _index: int = field(default=0, kw_only=True) - _title: str = field(default="") - _status: Status = field(default=Status.TODO, kw_only=True) - _destination: Destination = field(default=Destination.INBOX, kw_only=True) - _creation_date: datetime | None = field(factory=Util.now, kw_only=True) - _modification_date: datetime | None = field(factory=Util.now, kw_only=True) - _scheduled_date: datetime | None = field(default=None, kw_only=True) - _today_index_reference_date: datetime | None = field(default=None, kw_only=True) - _completion_date: datetime | None = field(default=None, kw_only=True) - _due_date: datetime | None = field(default=None, kw_only=True) - _trashed: bool = field(default=False, kw_only=True) - _instance_creation_paused: bool = field(default=False, kw_only=True) - _projects: list[str] = field(factory=list, kw_only=True) - _areas: list[str] = field(factory=list, kw_only=True) - _is_evening: bool = field(default=False, kw_only=True) - _tags: list[Any] = field(factory=list, kw_only=True) # TODO: set data type - _type: Type = field(default=Type.TASK, kw_only=True) - _due_date_suppression_date: datetime | None = field(default=None, kw_only=True) - _repeating_template: list[str] = field(factory=list, kw_only=True) - _repeater_migration_date: Any = field( - default=None, kw_only=True - ) # TODO: date type yet to be seen - _delegate: list[Any] = field( - factory=list, kw_only=True - ) # TODO: date type yet to be seen - _due_date_offset: int = field(default=0, kw_only=True) - _last_alarm_interaction_date: datetime | None = field(default=None, kw_only=True) - _action_group: list[str] = field(factory=list, kw_only=True) - _leaves_tombstone: bool = field(default=False, kw_only=True) - _instance_creation_count: int = field(default=0, kw_only=True) - _today_index: int = field(default=0, kw_only=True) - _reminder: time | None = field(default=None, kw_only=True) - _instance_creation_start_date: datetime | None = field(default=None, kw_only=True) - _repeater: Any = field(default=None, kw_only=True) # TODO: date type yet to be seen - _after_completion_reference_date: datetime | None = field( - default=None, kw_only=True +Timestamp = Annotated[ + datetime, + pydantic.PlainValidator( + TodoSerde.from_timestamp, json_schema_input_type=datetime | int + ), + pydantic.PlainSerializer(TodoSerde.timestamp_rounded), +] +BoolBit = Annotated[bool, pydantic.PlainSerializer(int)] + + +class TodoApiObject(pydantic.BaseModel): + model_config = pydantic.ConfigDict(populate_by_name=True) + + index: Annotated[int, pydantic.Field(alias="ix")] + title: Annotated[str, pydantic.Field(alias="tt")] + status: Annotated[Status, pydantic.Field(alias="ss")] + destination: Annotated[Destination, pydantic.Field(alias="st")] + creation_date: Annotated[Timestamp | None, pydantic.Field(alias="cd")] + modification_date: Annotated[Timestamp | None, pydantic.Field(alias="md")] + scheduled_date: Annotated[Timestamp | None, pydantic.Field(alias="sr")] + today_index_reference_date: Annotated[Timestamp | None, pydantic.Field(alias="tir")] + completion_date: Annotated[Timestamp | None, pydantic.Field(alias="sp")] + due_date: Annotated[Timestamp | None, pydantic.Field(alias="dd")] + trashed: Annotated[bool, pydantic.Field(alias="tr")] + instance_creation_paused: Annotated[bool, pydantic.Field(alias="icp")] + projects: Annotated[list[str], pydantic.Field(alias="pr")] + areas: Annotated[list[str], pydantic.Field(alias="ar")] + evening: Annotated[BoolBit, pydantic.Field(alias="sb")] + tags: Annotated[list[Any], pydantic.Field(alias="tg")] + type: Annotated[Type, pydantic.Field(alias="tp")] + due_date_suppression_date: Annotated[Timestamp | None, pydantic.Field(alias="dds")] + repeating_template: Annotated[list[str], pydantic.Field(alias="rt")] + repeater_migration_date: Annotated[ + Any, pydantic.Field(alias="rmd") + ] # TODO: date type yet to be seen + delegate: Annotated[ + list[Any], pydantic.Field(alias="dl") + ] # TODO: date type yet to be seen + due_date_offset: Annotated[int, pydantic.Field(alias="do")] + last_alarm_interaction_date: Annotated[ + Timestamp | None, pydantic.Field(alias="lai") + ] + action_group: Annotated[list[str], pydantic.Field(alias="agr")] + leaves_tombstone: Annotated[bool, pydantic.Field(alias="lt")] + instance_creation_count: Annotated[int, pydantic.Field(alias="icc")] + today_index: Annotated[int, pydantic.Field(alias="ti")] + reminder: Annotated[time | None, pydantic.Field(alias="ato")] + instance_creation_start_date: Annotated[ + Timestamp | None, pydantic.Field(alias="icsd") + ] + repeater: Annotated[Any, pydantic.Field(alias="rp")] + after_completion_reference_date: Annotated[ + Timestamp | None, pydantic.Field(alias="acrd") + ] + recurrence_rule: Annotated[str | None, pydantic.Field(alias="rr")] + note: Annotated[Note, pydantic.Field(alias="nt")] + + def to_todo(self) -> TodoItem: + todo = TodoItem( + index=self.index, + title=self.title, + creation_date=self.creation_date, + modification_date=self.modification_date, + scheduled_date=self.scheduled_date, + today_index_reference_date=self.today_index_reference_date, + completion_date=self.completion_date, + due_date=self.due_date, + trashed=self.trashed, + instance_creation_paused=self.instance_creation_paused, + tags=self.tags, + due_date_suppression_date=self.due_date_suppression_date, + repeating_template=self.repeating_template, + repeater_migration_date=self.repeater_migration_date, + delegate=self.delegate, + due_date_offset=self.due_date_offset, + last_alarm_interaction_date=self.last_alarm_interaction_date, + action_group=self.action_group, + leaves_tombstone=self.leaves_tombstone, + instance_creation_count=self.instance_creation_count, + today_index=self.today_index, + reminder=self.reminder, + instance_creation_start_date=self.instance_creation_start_date, + repeater=self.repeater, + after_completion_reference_date=self.after_completion_reference_date, + recurrence_rule=self.recurrence_rule, + note=self.note, + ) + todo._status = self.status + todo._destination = self.destination + todo._projects = self.projects + todo._areas = self.areas + todo._evening = self.evening + todo._type = self.type + todo._api_object = self + return todo + + +class TodoDeltaApiObject(pydantic.BaseModel): + model_config = pydantic.ConfigDict(populate_by_name=True) + + index: Annotated[int | None, pydantic.Field(alias="ix")] = None + title: Annotated[str | None, pydantic.Field(alias="tt")] = None + status: Annotated[Status | None, pydantic.Field(alias="ss")] = None + destination: Annotated[Destination | None, pydantic.Field(alias="st")] = None + creation_date: Annotated[Timestamp | None, pydantic.Field(alias="cd")] = None + modification_date: Annotated[Timestamp | None, pydantic.Field(alias="md")] = None + scheduled_date: Annotated[Timestamp | None, pydantic.Field(alias="sr")] = None + today_index_reference_date: Annotated[ + Timestamp | None, pydantic.Field(alias="tir") + ] = None + completion_date: Annotated[Timestamp | None, pydantic.Field(alias="sp")] = None + due_date: Annotated[Timestamp | None, pydantic.Field(alias="dd")] = None + trashed: Annotated[bool | None, pydantic.Field(alias="tr")] = None + instance_creation_paused: Annotated[bool | None, pydantic.Field(alias="icp")] = None + projects: Annotated[list[str] | None, pydantic.Field(alias="pr")] = None + areas: Annotated[list[str] | None, pydantic.Field(alias="ar")] = None + evening: Annotated[BoolBit | None, pydantic.Field(alias="sb")] = None + tags: Annotated[list[Any] | None, pydantic.Field(alias="tg")] = None + type: Annotated[Type | None, pydantic.Field(alias="tp")] = None + due_date_suppression_date: Annotated[ + Timestamp | None, pydantic.Field(alias="dds") + ] = None + repeating_template: Annotated[list[str] | None, pydantic.Field(alias="rt")] = None + repeater_migration_date: Annotated[Any | None, pydantic.Field(alias="rmd")] = None + delegate: Annotated[list[Any] | None, pydantic.Field(alias="dl")] = None + due_date_offset: Annotated[int | None, pydantic.Field(alias="do")] = None + last_alarm_interaction_date: Annotated[ + Timestamp | None, pydantic.Field(alias="lai") + ] = None + action_group: Annotated[list[str] | None, pydantic.Field(alias="agr")] = None + leaves_tombstone: Annotated[bool | None, pydantic.Field(alias="lt")] = None + instance_creation_count: Annotated[int | None, pydantic.Field(alias="icc")] = None + today_index: Annotated[int | None, pydantic.Field(alias="ti")] = None + reminder: Annotated[time | None, pydantic.Field(alias="ato")] = None + instance_creation_start_date: Annotated[ + Timestamp | None, pydantic.Field(alias="icsd") + ] = None + repeater: Annotated[Any | None, pydantic.Field(alias="rp")] = None + after_completion_reference_date: Annotated[ + Timestamp | None, pydantic.Field(alias="acrd") + ] = None + recurrence_rule: Annotated[str | None, pydantic.Field(alias="rr")] = None + note: Annotated[Note | None, pydantic.Field(alias="nt")] = None + + def apply_edits(self, todo: TodoItem) -> None: + keys = self.model_dump(by_alias=False, exclude_none=True).keys() + if not keys: + raise RuntimeError("there are no edits to apply") + for key in keys: + old_value = getattr(todo, key) + new_value = getattr(self, key) + if old_value == new_value: + msg = f"old and new value are identical: {new_value}" + raise ValueError(msg) + setattr(todo, key, new_value) + + +class TodoItem(pydantic.BaseModel): + model_config = pydantic.ConfigDict(validate_assignment=True) + + _uuid: ShortUUID = pydantic.PrivateAttr(default_factory=Util.uuid) + index: int = pydantic.Field(default=0) + title: str + _status: Status = pydantic.PrivateAttr(default=Status.TODO) + _destination: Destination = pydantic.PrivateAttr(default=Destination.INBOX) + creation_date: datetime | None = pydantic.Field(default_factory=Util.now) + modification_date: datetime | None = pydantic.Field(default_factory=Util.now) + scheduled_date: datetime | None = pydantic.Field(default=None) + today_index_reference_date: datetime | None = pydantic.Field( + default=None, repr=False ) - _recurrence_rule: str | None = field( - default=None, kw_only=True - ) # TODO: weird XML values - _note: Note = field(factory=Note, kw_only=True) - _changes: Deque[str] = field(factory=Deque, init=False) - - @property - def uuid(self) -> str: - return self._uuid - - @property - def changes(self) -> set: - return set(self._changes) - - def reset_changes(self) -> None: - self._changes.clear() + completion_date: datetime | None = pydantic.Field(default=None) + due_date: datetime | None = pydantic.Field(default=None) + trashed: bool = pydantic.Field(default=False) + instance_creation_paused: bool = pydantic.Field(default=False) + _projects: list[str] = pydantic.PrivateAttr(default_factory=list) + _areas: list[str] = pydantic.PrivateAttr(default_factory=list) + _evening: bool = pydantic.PrivateAttr(default=False) + tags: list[Any] = pydantic.Field(default_factory=list) # TODO: set data type + _type: Type = pydantic.PrivateAttr(default=Type.TASK) + due_date_suppression_date: datetime | None = pydantic.Field(default=None) + repeating_template: list[str] = pydantic.Field(default_factory=list) + repeater_migration_date: Any = pydantic.Field(default=None) + delegate: list[Any] = pydantic.Field(default_factory=list) + due_date_offset: int = pydantic.Field(default=0) + last_alarm_interaction_date: datetime | None = pydantic.Field(default=None) + action_group: list[str] = pydantic.Field(default_factory=list) + leaves_tombstone: bool = pydantic.Field(default=False) + instance_creation_count: int = pydantic.Field(default=0) + today_index: int = pydantic.Field(default=0) + reminder: time | None = pydantic.Field(default=None) + instance_creation_start_date: datetime | None = pydantic.Field(default=None) + repeater: Any = pydantic.Field(default=None) # TODO: date type yet to be seen + after_completion_reference_date: datetime | None = pydantic.Field(default=None) + recurrence_rule: str | None = pydantic.Field(default=None) # TODO: weird XML values + note: Note = pydantic.Field(default_factory=Note) + _api_object: TodoApiObject | None = pydantic.PrivateAttr(default=None) + + def to_update(self) -> Update: + if not self._api_object: + complete = self._to_new() + body = NewBody(payload=complete) + update = Update(id=self.uuid, body=body) + else: + delta = self._to_edit() + body = EditBody(payload=delta) + update = Update(id=self.uuid, body=body) + return update + + def _to_new(self) -> TodoApiObject: + if self._api_object: + msg = ( + f"current version exists for todo, use {self._to_edit.__name__} instead" + ) + raise ValueError(msg) + + return TodoApiObject( + index=self.index, + title=self.title, + status=self.status, + destination=self.destination, + creation_date=self.creation_date, + modification_date=self.modification_date, + scheduled_date=self.scheduled_date, + today_index_reference_date=self.today_index_reference_date, + completion_date=self.completion_date, + due_date=self.due_date, + trashed=self.trashed, + instance_creation_paused=self.instance_creation_paused, + projects=self._projects, + areas=self._areas, + evening=self.is_evening, + tags=self.tags, + type=self._type, + due_date_suppression_date=self.due_date_suppression_date, + repeating_template=self.repeating_template, + repeater_migration_date=self.repeater_migration_date, + delegate=self.delegate, + due_date_offset=self.due_date_offset, + last_alarm_interaction_date=self.last_alarm_interaction_date, + action_group=self.action_group, + leaves_tombstone=self.leaves_tombstone, + instance_creation_count=self.instance_creation_count, + today_index=self.today_index, + reminder=self.reminder, + instance_creation_start_date=self.instance_creation_start_date, + repeater=self.repeater, + after_completion_reference_date=self.after_completion_reference_date, + recurrence_rule=self.recurrence_rule, + note=self.note, + ) - # @changes.deleter - # def changes(self) -> None: - # self._changes.clear() + def _to_edit(self) -> TodoDeltaApiObject: + if not self._api_object: + msg = f"no current version exists for todo, use {self._to_new.__name__} instead" + raise ValueError(msg) + + keys = self.model_dump(by_alias=False).keys() + edits = {} + for key in keys: + current_value = getattr(self._api_object, key) + new_value = getattr(self, key) + if current_value == new_value: + continue + edits[key] = new_value + if not edits: + raise ValueError("no changes found") + return TodoDeltaApiObject.model_validate(edits) + + def _commit(self, complete_or_delta: TodoApiObject | TodoDeltaApiObject) -> None: + if isinstance(complete_or_delta, TodoApiObject): + self._api_object = complete_or_delta + + else: + delta = complete_or_delta.model_dump(by_alias=False, exclude_none=True) + for key in delta.keys(): + new_value = getattr(delta, key) + setattr(self._api_object, key, new_value) @property - def title(self) -> str: - return self._title - - @title.setter - @mod("_title") - def title(self, title: str) -> None: - self._title = title + def uuid(self) -> ShortUUID: + return self._uuid + @pydantic.computed_field @property - def destination(self) -> Destination: - return self._destination - - @destination.setter - @mod("_destination") - def destination(self, destination: Destination) -> None: - self._destination = destination + def type(self) -> Type: + return self._type @property def project(self) -> str | None: return self._projects[0] if self._projects else None @project.setter - @mod("_projects") def project(self, project: TodoItem | str | None) -> None: if isinstance(project, TodoItem): - if project._type != Type.PROJECT: + if project.type is not Type.PROJECT: raise ValueError("argument must be a project") + if self.uuid == project.uuid: + raise ValueError("cannot assign self as project") self._projects = [project.uuid] elif project: + if self.uuid == project: + raise ValueError("cannot assign self as project") self._projects = [project] if not project: @@ -164,7 +429,7 @@ def project(self, project: TodoItem | str | None) -> None: if self.area: self.area = None # move out of inbox - if self.destination == Destination.INBOX: + if self.destination is Destination.INBOX: self.destination = Destination.ANYTIME @property @@ -172,7 +437,6 @@ def area(self) -> str | None: return self._areas[0] if self._areas else None @area.setter - @mod("_areas") def area(self, area: str | None) -> None: if not area: self._areas.clear() @@ -183,15 +447,15 @@ def area(self, area: str | None) -> None: if self.project: self.project = None # move out of inbox - if self.destination == Destination.INBOX: + if self.destination is Destination.INBOX: self.destination = Destination.ANYTIME + @pydantic.computed_field @property def status(self) -> Status: return self._status @status.setter - @mod("_status") def status(self, status: Status) -> None: if self._status is status: raise ValueError(f"item already has {status.name.lower()} status") @@ -211,58 +475,41 @@ def complete(self) -> None: def cancel(self) -> None: self.status = Status.CANCELLED - @mod("_trashed") + @pydantic.computed_field + @property + def destination(self) -> Destination: + return self._destination + + @destination.setter + def destination(self, destination: Destination) -> None: + if self.type is not Type.TASK: + raise ValueError("destination can only be changed for a task") + self._destination = destination + def delete(self) -> None: - self._trashed = True + if self.trashed: + raise ValueError("item is already trashed") + self.trashed = True - @mod("_trashed") def restore(self) -> None: - self._trashed = False + if not self.trashed: + raise ValueError("item is not trashed") + self.trashed = False - @mod("_type", "_instance_creation_paused") def as_project(self) -> TodoItem: + if self._type is not Type.TASK: + raise ValueError("only a task can be converted to project") self._type = Type.PROJECT - self._instance_creation_paused = True - if self.destination == Destination.INBOX: - self.destination = Destination.ANYTIME + self.instance_creation_paused = True + if self._destination is Destination.INBOX: + self._destination = Destination.ANYTIME return self - @property - def completion_date(self) -> datetime | None: - return self._completion_date - - @completion_date.setter - @mod("_completion_date") - def completion_date(self, completion_date: datetime | None) -> None: - self._completion_date = completion_date - - @property - def scheduled_date(self) -> datetime | None: - return self._scheduled_date - - @scheduled_date.setter - @mod("_scheduled_date", "_today_index_reference_date") - def scheduled_date(self, scheduled_date: datetime | None) -> None: - self._scheduled_date = scheduled_date - self._today_index_reference_date = scheduled_date - - @property - def due_date(self) -> datetime | None: - return self._due_date - - @due_date.setter - @mod("_due_date") - def due_date(self, deadline: datetime | None) -> None: - self._due_date = deadline - - @property - def reminder(self) -> time | None: - return self._reminder - - @reminder.setter - @mod("_reminder") - def reminder(self, reminder: time | None) -> None: - self._reminder = reminder + # @scheduled_date.setter + # @mod("scheduled_date_", "today_index_reference_date_") + # def scheduled_date(self, scheduled_date: datetime | None) -> None: + # self.scheduled_date_ = scheduled_date + # self.today_index_reference_date_ = scheduled_date @property def is_today(self) -> bool: @@ -273,135 +520,13 @@ def is_today(self) -> bool: @property def is_evening(self) -> bool: - return self.is_today and self._is_evening + return self.is_today and self._evening - @mod("_is_evening") def evening(self) -> None: self.today() - self._is_evening = True + self._evening = True - @mod() def today(self) -> None: today = Util.today() self.destination = Destination.ANYTIME self.scheduled_date = today - - def update(self, update: TodoItem, keys: set[str]) -> None: - for key in translate_keys_deserialize(keys): - val = getattr(update, key) - setattr(self, key, val) - - -converter = cattrs.Converter(forbid_extra_keys=True) -todo_unst_hook = make_dict_unstructure_fn( - TodoItem, - converter, - _uuid=override(omit=True), - _changes=override(omit=True), -) - -todo_st_hook = make_dict_structure_fn( - TodoItem, - converter, - _index=override(rename="ix"), - _title=override(rename="tt"), - _status=override(rename="ss"), - _destination=override(rename="st"), - _creation_date=override(rename="cd"), - _modification_date=override(rename="md"), - _scheduled_date=override(rename="sr"), - _today_index_reference_date=override(rename="tir"), - _completion_date=override(rename="sp"), - _due_date=override(rename="dd"), - _in_trash=override(rename="tr"), - _instance_creation_paused=override(rename="icp"), - _projects=override(rename="pr"), - _areas=override(rename="ar"), - _is_evening=override( - rename="sb", struct_hook=lambda value, _: bool(value), unstruct_hook=int - ), - _tags=override(rename="tg"), - _type=override(rename="tp"), - _due_date_suppression_date=override(rename="dds"), - _repeating_template=override(rename="rt"), - _repeater_migration_date=override(rename="rmd"), - _delegate=override(rename="dl"), - _due_date_offset=override(rename="do"), - _last_alarm_interaction_date=override(rename="lai"), - _action_group=override(rename="agr"), - _leaves_tombstone=override(rename="lt"), - _instance_creation_count=override(rename="icc"), - _today_index=override(rename="ti"), - _reminder=override(rename="ato"), - _instance_creation_start_date=override(rename="icsd"), - _repeater=override(rename="rp"), - _after_completion_reference_date=override(rename="acrd"), - _recurrence_rule=override(rename="rr"), - _note=override(rename="nt"), -) - - -converter.register_unstructure_hook(TodoItem, todo_unst_hook) -converter.register_structure_hook(TodoItem, todo_st_hook) - -converter.register_unstructure_hook(datetime, TodoSerde.timestamp_rounded) -converter.register_structure_hook( - datetime, lambda timestamp, _: datetime.fromtimestamp(timestamp, timezone.utc) -) - -ALIASES_UNSTRUCT = { - "_index": "ix", - "_title": "tt", - "_status": "ss", - "_destination": "st", - "_creation_date": "cd", - "_modification_date": "md", - "_scheduled_date": "sr", - "_today_index_reference_date": "tir", - "_completion_date": "sp", - "_due_date": "dd", - "_trashed": "tr", - "_instance_creation_paused": "icp", - "_projects": "pr", - "_areas": "ar", - "_is_evening": "sb", - "_tags": "tg", - "_type": "tp", - "_due_date_suppression_date": "dds", - "_repeating_template": "rt", - "_repeater_migration_date": "rmd", - "_delegate": "dl", - "_due_date_offset": "do", - "_last_alarm_interaction_date": "lai", - "_action_group": "agr", - "_leaves_tombstone": "lt", - "_instance_creation_count": "icc", - "_today_index": "ti", - "_reminder": "ato", - "_instance_creation_start_date": "icsd", - "_repeater": "rp", - "_after_completion_reference_date": "acrd", - "_recurrence_rule": "rr", - "_note": "nt", -} - -ALIASES_STRUCT = {v: k for k, v in ALIASES_UNSTRUCT.items()} - - -def get_changes(todo: TodoItem) -> dict: - return serialize_dict(todo, todo.changes) - - -def serialize_dict(todo: TodoItem, keys: set[str] | None = None) -> dict: - # d = asdict(todo) - d = converter.unstructure(todo) - # filter allowed keys - return {ALIASES_UNSTRUCT[k]: v for k, v in d.items() if keys is None or k in keys} - - -def deserialize(api_object: dict) -> TodoItem: - return converter.structure(api_object, TodoItem) - - -def translate_keys_deserialize(keys: set[str]) -> set[str]: - return {ALIASES_STRUCT[k] for k in keys} diff --git a/things_cloud/utils.py b/things_cloud/utils.py index 4cbf52d..a340dee 100644 --- a/things_cloud/utils.py +++ b/things_cloud/utils.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta, timezone from shortuuid import ShortUUID @@ -6,7 +6,7 @@ class Util: @staticmethod def now() -> datetime: - return datetime.utcnow() + return datetime.now(UTC) @staticmethod def today() -> datetime: