From 9fdd228fbdf75f5622fa2778ec5be777ffca9c6f Mon Sep 17 00:00:00 2001 From: Randy Syring Date: Mon, 10 Jun 2024 01:24:58 -0400 Subject: [PATCH] Refinements - Automatic dependency detection - Better readme --- .github/workflows/pypi.yaml | 1 - .gitignore | 4 +- .pre-commit-config.yaml | 4 +- ci/pytest.ini | 12 - env-config.yaml | 4 + noxfile.py | 9 +- readme.md | 32 +- requirements/base.in | 8 +- requirements/base.txt | 112 +----- requirements/ci.txt | 36 +- requirements/dev.in | 3 +- requirements/dev.txt | 343 ++++++++---------- requirements/testing.in | 6 + requirements/testing.txt | 114 ++++++ src/conftest.py | 9 + src/reqs/cli.py | 22 +- src/reqs/config.py | 44 +-- .../requirements/base.in => libs/__init__.py} | 0 src/reqs/libs/testing.py | 83 +++++ src/reqs/tests/pkg-upgrade/pyproject.toml | 0 .../tests/pkg-upgrade/requirements/alpha.in | 4 + .../tests/pkg-upgrade/requirements/bravo.in | 3 + .../tests/pkg-upgrade/requirements/charlie.in | 2 + src/reqs/tests/pkg1/requirements/alpha.in | 2 + src/reqs/tests/pkg1/requirements/bravo.in | 1 + src/reqs/tests/pkg1/requirements/charlie.in | 1 + src/reqs/tests/pkg1/requirements/ci.in | 1 - src/reqs/tests/pkg1/requirements/dev.in | 2 - src/reqs/tests/pkg2/pyproject.toml | 4 - src/reqs/tests/pkg4/requirements/base.in | 1 + src/reqs/tests/pkg4/requirements/foo.in | 1 + src/reqs/tests/pkg5/pyproject.toml | 0 src/reqs/tests/pkg5/requirements/alpha.in | 2 + src/reqs/tests/pkg5/requirements/bravo.in | 1 + src/reqs/tests/test_cli.py | 147 ++++---- src/reqs/tests/test_config.py | 49 --- src/reqs/tests/test_utils.py | 140 +++++++ src/reqs/utils.py | 142 ++++++-- 38 files changed, 789 insertions(+), 560 deletions(-) delete mode 100644 ci/pytest.ini create mode 100644 env-config.yaml create mode 100644 requirements/testing.in create mode 100644 requirements/testing.txt create mode 100644 src/conftest.py rename src/reqs/{tests/pkg1/requirements/base.in => libs/__init__.py} (100%) create mode 100644 src/reqs/libs/testing.py create mode 100644 src/reqs/tests/pkg-upgrade/pyproject.toml create mode 100644 src/reqs/tests/pkg-upgrade/requirements/alpha.in create mode 100644 src/reqs/tests/pkg-upgrade/requirements/bravo.in create mode 100644 src/reqs/tests/pkg-upgrade/requirements/charlie.in create mode 100644 src/reqs/tests/pkg1/requirements/alpha.in create mode 100644 src/reqs/tests/pkg1/requirements/bravo.in create mode 100644 src/reqs/tests/pkg1/requirements/charlie.in delete mode 100644 src/reqs/tests/pkg1/requirements/ci.in delete mode 100644 src/reqs/tests/pkg1/requirements/dev.in create mode 100644 src/reqs/tests/pkg5/pyproject.toml create mode 100644 src/reqs/tests/pkg5/requirements/alpha.in create mode 100644 src/reqs/tests/pkg5/requirements/bravo.in delete mode 100644 src/reqs/tests/test_config.py create mode 100644 src/reqs/tests/test_utils.py diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml index 0dbfb4e..d6c59b0 100644 --- a/.github/workflows/pypi.yaml +++ b/.github/workflows/pypi.yaml @@ -61,4 +61,3 @@ jobs: - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - diff --git a/.gitignore b/.gitignore index 925c9a7..12f9859 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ *.coverage /.tox /.nonx -/coverage.xml +/coverage*.xml /.pytests.xml /htmlcov .pytest_cache @@ -19,3 +19,5 @@ # Project builds dist + +src/reqs/tests/pkg*/requirements/*.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a1a294..d7eab5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: check-added-large-files - id: check-yaml - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.4.4 + rev: v0.4.8 hooks: # i.e. `ruff check` - id: ruff @@ -25,6 +25,6 @@ repos: # the linting check above would always catch a problem created by the formatter. args: [ --check ] - repo: https://github.com/level12/pre-commit-hooks - rev: v0.20240516.2 + rev: v0.20240606.1 hooks: - id: check-ruff-versions diff --git a/ci/pytest.ini b/ci/pytest.ini deleted file mode 100644 index 67ba2e1..0000000 --- a/ci/pytest.ini +++ /dev/null @@ -1,12 +0,0 @@ -[pytest] -filterwarnings = - # Any errors not noted here should cause pytest to throw an error. It seems like this should be - # last in the list, but warnings that match multiple lines will apply the last line matched. - error - - # Example warning filters - # ignore:Predicate of partial index.*ignored during reflection - # ignore:Request.is_xhr is deprecated - # ignore:Client.get_ident is deprecated. The event ID is now returned as the result of capture. - # ignore:Using or importing the ABCs from 'collections' - # ignore::DeprecationWarning:webtest.*: diff --git a/env-config.yaml b/env-config.yaml new file mode 100644 index 0000000..f1d3be5 --- /dev/null +++ b/env-config.yaml @@ -0,0 +1,4 @@ +profile: + pypi: + HATCH_INDEX_USER: '__TOKEN__' + HATCH_INDEX_AUTH: 'op://private/pypi.python.org/api-token' diff --git a/noxfile.py b/noxfile.py index 14929dc..c761de5 100644 --- a/noxfile.py +++ b/noxfile.py @@ -11,13 +11,11 @@ @nox.session -def tests(session: nox.Session): - session.install('-r', 'requirements/base.txt') - session.install('e', '.') +def pytest(session: nox.Session): + session.install('-r', 'requirements/testing.txt') + session.install('-e', '.') session.run( 'pytest', - # use our pytest.ini for warning management - '-c=ci/pytest.ini', '-ra', '--tb=native', '--strict-markers', @@ -37,6 +35,5 @@ def standards(session: nox.Session): session.run( 'pre-commit', 'run', - '--show-diff-on-failure', '--all-files', ) diff --git a/readme.md b/readme.md index b701948..15b1ba5 100644 --- a/readme.md +++ b/readme.md @@ -1,21 +1,27 @@ reqs ==== -Bootstrap, compile, and sync Python requirements files +Helps with Python requirements (reqs) files: -# Install - -Intended to be used with pipx +- `reqs bootstrap`: + - Install uv (default) or upgrade pip & install pip-tools to active venv + - Ensures reqs will compile lock files using the version of Python the project is using +- `reqs compile`: + - Compile .in reqs files into .txt "lock" files + - Considers file modification times and file dependencies (when -r or -c used) +- `reqs sync`: + - Compile (default, optional) + - Sync active virtualenv with lock files + - When "sync_pipx" is true: will make the project's scripts available on the local system by + installing/upgrading as an editable package with pipx. -- manually & first install: `pipx install -e .../apps/reqs-pkg`; or -- when developing: `cd .../apps/reqs-pkg;` [`reqs`](../reqs-pkg/) `sync` +# Install -## Usage +Intended to be installed at the user level, not per app. -- `reqs bootstrap`: Upgrade pip & install pip-tools -- `reqs compile`: Compile .in to .txt when needed (based on file modification times) -- `reqs sync`: Compile and then update active venv and maybe pipx to match spec +- `pipx install reqs-cli` (recommended) +- `[uv] pip install --user reqs-cli` # Configuration @@ -35,10 +41,4 @@ dpath = 'requirements' # that a developer would want available for different projects. False for most client projects # deployed on servers. sync_pipx = false - -[tool.reqs.depends] -# Define dependencies between files so `reqs compile` knows when a .in needs to be compiled and -# what order to use when compiling multiple files. -'base.in' = '' -'dev.in' = 'base.txt' ``` diff --git a/requirements/base.in b/requirements/base.in index 0bc7daa..949072f 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,7 +1,3 @@ -# From copier-py-package (keep comment for easier copier upgrades) -pytest -pytest-cov - -# App specific (ditto) +# App specific click -pip-tools +pip-requirements-parser diff --git a/requirements/base.txt b/requirements/base.txt index 7cd876e..eb706f5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,110 +1,16 @@ -build==1.2.1 \ - --hash=sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d \ - --hash=sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4 - # via pip-tools click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de - # via - # -r requirements/base.in - # pip-tools -coverage==7.5.1 \ - --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ - --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ - --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ - --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ - --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ - --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ - --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ - --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ - --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ - --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ - --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ - --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ - --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ - --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ - --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ - --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ - --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ - --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ - --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ - --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ - --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ - --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ - --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ - --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ - --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ - --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ - --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ - --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ - --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ - --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ - --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ - --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ - --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ - --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ - --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ - --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ - --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ - --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ - --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ - --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ - --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ - --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ - --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ - --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ - --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ - --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ - --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ - --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ - --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ - --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ - --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ - --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece - # via pytest-cov -iniconfig==2.0.0 \ - --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ - --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 - # via pytest + # via -r requirements/base.in packaging==24.0 \ --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 - # via - # build - # pytest -pip==24.0 \ - --hash=sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc \ - --hash=sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2 - # via pip-tools -pip-tools==7.4.1 \ - --hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \ - --hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9 - # via -r requirements/base.in -pluggy==1.5.0 \ - --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ - --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 - # via pytest -pyproject-hooks==1.1.0 \ - --hash=sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965 \ - --hash=sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2 - # via - # build - # pip-tools -pytest==8.2.0 \ - --hash=sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233 \ - --hash=sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f - # via - # -r requirements/base.in - # pytest-cov -pytest-cov==5.0.0 \ - --hash=sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652 \ - --hash=sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857 + # via pip-requirements-parser +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 # via -r requirements/base.in -setuptools==69.5.1 \ - --hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \ - --hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32 - # via pip-tools -wheel==0.43.0 \ - --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ - --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 - # via pip-tools +pyparsing==3.1.2 \ + --hash=sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad \ + --hash=sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742 + # via pip-requirements-parser diff --git a/requirements/ci.txt b/requirements/ci.txt index b19f456..d6ab319 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -40,24 +40,24 @@ platformdirs==4.2.2 \ # via # -c requirements/dev.txt # virtualenv -uv==0.1.44 \ - --hash=sha256:012fcfc3789f303ee3ff9f2a6e09bc589710fed7c2dcbad4379832072bad7a95 \ - --hash=sha256:05774eb086b18aad488c3140daa62a235e3f270f62bd3cc4aaaa54eed927cc5b \ - --hash=sha256:26d07edb37e7bfddc3b4e1faa13420e6048ddd974b34fbc1c19fcf9bacef9e5b \ - --hash=sha256:2e5a60af214f42b621aa37ad320253c64f77dbfacafa710dc42d34965c2cd27f \ - --hash=sha256:2f95acffcdac507de9c8f8ed037e529df3ccea274b4453df05df3f331543f5fc \ - --hash=sha256:3eeac3d8be69831430743f3d00f84ddccfbd56b6835bb52d17f97914c9adfdff \ - --hash=sha256:567486ce0ad2f9778782ba6ee19d2b65516c4f4bf2b7b4fc66fc2712cd46c6d0 \ - --hash=sha256:768369a0bbdea8c3a670388ec1b4a11fe5871ef40d84a43844e9b8d97a1c2ca5 \ - --hash=sha256:7983b00d95290dcdea8488fa8ecdfdef5c8e7d3c92c90b8dcf405cc26b707add \ - --hash=sha256:8f90e80e11da409ce88424381f5c91e7f908d6a7eec53ed4ae60c5d76698d126 \ - --hash=sha256:9ef3448111b47ab95874fbf2c5ca8efd52f54de14086079e52b588d037d243f1 \ - --hash=sha256:b04eb0c8dedadfe434f9756bdc1c8a09a75df83884ba4cc7d97985ee819e4f32 \ - --hash=sha256:b076828cef1f1ae1c3b54fa97b9e16b32816acc521ca6ff4a54fd8b16df67eef \ - --hash=sha256:d60e5b77b958c559324882da13ffa642dcd511e6a7eb9b07e7308a6d71e248de \ - --hash=sha256:d82c7338f8bcb0551672e759e4115c035246321059692416ee03ebe08629b913 \ - --hash=sha256:e247dca0d8d42d71032ac99ef3d72a4fcbad4ae3114ef5979878a81a40fed274 \ - --hash=sha256:e8cb1047b8f81ef09e15ec8d1b8dfc371594232e2e4f3ef3acf8991fcda20a57 +uv==0.2.9 \ + --hash=sha256:1942808d1df3fab9482c5ab47484a7a5eb75aacea9b27c2cbbfb60cb826f4c56 \ + --hash=sha256:22deea3d7f83845a54503d1c2d5ff49d28cff109123df415be4496cf1c851b2e \ + --hash=sha256:23cf2e847a0e02e2ddc938c164dbec4878583c61fabb0a1134d00fc9ebf92076 \ + --hash=sha256:290c23ed4d2a16500c685e347326c2137931350f7148e41bb04941a9d90e2d45 \ + --hash=sha256:326d88b12745558eb2b49968c03e088ccab3a8860a7962ee3a0fcfc1289fdb41 \ + --hash=sha256:361368cd38c0d5595374bc22c4733d8c2252c00649158f2168f894a098823c5e \ + --hash=sha256:3a9914fab493978eed5781b3fc13126836472f1a91f1092c07d825150405a42a \ + --hash=sha256:5b0c27f12ec91e8ee6066fd8ee0b3405a791350e8898cdfa99177048c3b364a6 \ + --hash=sha256:6251f4d7d77d29aab625b02a93160ede1ba9edd766918da5064552219561e245 \ + --hash=sha256:692fed655d5cbb457fa2762085bb49f9a74a13ad1608ebfa9b1bd81ed09bdaee \ + --hash=sha256:6b5f4f612c3f2008314fcd2415a1d0c22050d87324476aa8907695723c72483b \ + --hash=sha256:718d3e5e46084c7c6305a20789e27eeea2171a85ef3af37b33324a30b04dedc8 \ + --hash=sha256:8a345a0e4d5b4a31297bb5aea9ef5dd84d91faff1dcb7939e263d4e7b344ae15 \ + --hash=sha256:8ccb4e20606a1cd710573c63b0d79c9ae7d3fbb97d7962efb05344d9b9c83f6b \ + --hash=sha256:92e0af80ab5c40bed88064ff1adc95f587c501ed8371077330703edfafe78661 \ + --hash=sha256:c4d6914fd31213512e75d57ece4efed443aec669fd23ed83c098530a3bf7f39f \ + --hash=sha256:e63daf67b3e6922a58b571a5cf63605e737cb2f34b48973014cc0fd4e8528ecd # via # -c requirements/dev.txt # nox diff --git a/requirements/dev.in b/requirements/dev.in index 3beb838..6c33d6d 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -1,7 +1,6 @@ --r base.txt +-r testing.txt # From copier-py-package (keep comment for easier copier upgrades) -click hatch nox[uv] pre-commit diff --git a/requirements/dev.txt b/requirements/dev.txt index 6e6ae14..726370e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,20 +1,14 @@ -anyio==4.3.0 \ - --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \ - --hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 +anyio==4.4.0 \ + --hash=sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94 \ + --hash=sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7 # via httpx argcomplete==3.3.0 \ --hash=sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54 \ --hash=sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62 # via nox -build==1.2.1 \ - --hash=sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d \ - --hash=sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4 - # via - # -r requirements/base.txt - # pip-tools -certifi==2024.2.2 \ - --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ - --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 +certifi==2024.6.2 \ + --hash=sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516 \ + --hash=sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56 # via # httpcore # httpx @@ -80,104 +74,102 @@ click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de # via - # -r requirements/base.txt - # -r requirements/dev.in + # -r requirements/testing.txt # hatch - # pip-tools # userpath colorlog==6.8.2 \ --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 # via nox -coverage==7.5.1 \ - --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ - --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ - --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ - --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ - --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ - --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ - --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ - --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ - --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ - --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ - --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ - --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ - --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ - --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ - --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ - --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ - --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ - --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ - --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ - --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ - --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ - --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ - --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ - --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ - --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ - --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ - --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ - --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ - --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ - --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ - --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ - --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ - --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ - --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ - --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ - --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ - --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ - --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ - --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ - --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ - --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ - --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ - --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ - --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ - --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ - --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ - --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ - --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ - --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ - --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ - --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ - --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece +coverage==7.5.3 \ + --hash=sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523 \ + --hash=sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f \ + --hash=sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d \ + --hash=sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb \ + --hash=sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0 \ + --hash=sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c \ + --hash=sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98 \ + --hash=sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83 \ + --hash=sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8 \ + --hash=sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7 \ + --hash=sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac \ + --hash=sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84 \ + --hash=sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb \ + --hash=sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3 \ + --hash=sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884 \ + --hash=sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614 \ + --hash=sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd \ + --hash=sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807 \ + --hash=sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd \ + --hash=sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8 \ + --hash=sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc \ + --hash=sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db \ + --hash=sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0 \ + --hash=sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08 \ + --hash=sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232 \ + --hash=sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d \ + --hash=sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a \ + --hash=sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1 \ + --hash=sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286 \ + --hash=sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303 \ + --hash=sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341 \ + --hash=sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84 \ + --hash=sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45 \ + --hash=sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc \ + --hash=sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec \ + --hash=sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd \ + --hash=sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155 \ + --hash=sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52 \ + --hash=sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d \ + --hash=sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485 \ + --hash=sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31 \ + --hash=sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d \ + --hash=sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d \ + --hash=sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d \ + --hash=sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85 \ + --hash=sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce \ + --hash=sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb \ + --hash=sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974 \ + --hash=sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24 \ + --hash=sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56 \ + --hash=sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9 \ + --hash=sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35 # via - # -r requirements/base.txt + # -r requirements/testing.txt # pytest-cov -cryptography==42.0.7 \ - --hash=sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55 \ - --hash=sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785 \ - --hash=sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b \ - --hash=sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886 \ - --hash=sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82 \ - --hash=sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1 \ - --hash=sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda \ - --hash=sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f \ - --hash=sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68 \ - --hash=sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60 \ - --hash=sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7 \ - --hash=sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd \ - --hash=sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582 \ - --hash=sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc \ - --hash=sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858 \ - --hash=sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b \ - --hash=sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2 \ - --hash=sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678 \ - --hash=sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13 \ - --hash=sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4 \ - --hash=sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8 \ - --hash=sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604 \ - --hash=sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477 \ - --hash=sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e \ - --hash=sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a \ - --hash=sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9 \ - --hash=sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14 \ - --hash=sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda \ - --hash=sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da \ - --hash=sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562 \ - --hash=sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2 \ - --hash=sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9 +cryptography==42.0.8 \ + --hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \ + --hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \ + --hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \ + --hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \ + --hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \ + --hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \ + --hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \ + --hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \ + --hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \ + --hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \ + --hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \ + --hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \ + --hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \ + --hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \ + --hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \ + --hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \ + --hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \ + --hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \ + --hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \ + --hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \ + --hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \ + --hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \ + --hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \ + --hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \ + --hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \ + --hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \ + --hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \ + --hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \ + --hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \ + --hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \ + --hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \ + --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e # via secretstorage distlib==0.3.8 \ --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ @@ -191,9 +183,9 @@ h11==0.14.0 \ --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 # via httpcore -hatch==1.11.0 \ - --hash=sha256:0511e48b06576995a51a39af14c062431cbbbf22df8a4faccce41418ebccab30 \ - --hash=sha256:3949517cee5fdd520829b31a81c6bcd63e18750c0db0171d541bd17cdfea15c0 +hatch==1.12.0 \ + --hash=sha256:7df02b2df8b2364c33f1cadab4966ae24d8dd235edd61b21ed9c2975506e4174 \ + --hash=sha256:ae80478d10312df2b44d659c93bc2ed4d33aecddce4b76378231bdf81c8bf6ad # via -r requirements/dev.in hatchling==1.24.2 \ --hash=sha256:41ddc27cdb25db9ef7b68bef075f829c84cb349aa1bff8240797d012510547b0 \ @@ -226,7 +218,7 @@ iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via - # -r requirements/base.txt + # -r requirements/testing.txt # pytest jaraco-classes==3.4.0 \ --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ @@ -264,9 +256,9 @@ more-itertools==10.2.0 \ # via # jaraco-classes # jaraco-functools -nodeenv==1.8.0 \ - --hash=sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2 \ - --hash=sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec +nodeenv==1.9.1 \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 # via pre-commit nox==2024.4.15 \ --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ @@ -276,11 +268,11 @@ packaging==24.0 \ --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 # via - # -r requirements/base.txt - # build + # -r requirements/testing.txt # hatch # hatchling # nox + # pip-requirements-parser # pytest pathspec==0.12.1 \ --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ @@ -290,16 +282,10 @@ pexpect==4.9.0 \ --hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \ --hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f # via hatch -pip==24.0 \ - --hash=sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc \ - --hash=sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2 - # via - # -r requirements/base.txt - # pip-tools -pip-tools==7.4.1 \ - --hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \ - --hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9 - # via -r requirements/base.txt +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via -r requirements/testing.txt platformdirs==4.2.2 \ --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 @@ -310,7 +296,7 @@ pluggy==1.5.0 \ --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 # via - # -r requirements/base.txt + # -r requirements/testing.txt # hatchling # pytest pre-commit==3.7.1 \ @@ -329,23 +315,22 @@ pygments==2.18.0 \ --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a # via rich -pyproject-hooks==1.1.0 \ - --hash=sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965 \ - --hash=sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2 +pyparsing==3.1.2 \ + --hash=sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad \ + --hash=sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742 # via - # -r requirements/base.txt - # build - # pip-tools -pytest==8.2.0 \ - --hash=sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233 \ - --hash=sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f + # -r requirements/testing.txt + # pip-requirements-parser +pytest==8.2.2 \ + --hash=sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343 \ + --hash=sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977 # via - # -r requirements/base.txt + # -r requirements/testing.txt # pytest-cov pytest-cov==5.0.0 \ --hash=sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652 \ --hash=sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857 - # via -r requirements/base.txt + # via -r requirements/testing.txt pyyaml==6.0.1 \ --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ @@ -403,36 +388,29 @@ rich==13.7.1 \ --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 # via hatch -ruff==0.4.4 \ - --hash=sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768 \ - --hash=sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6 \ - --hash=sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6 \ - --hash=sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae \ - --hash=sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15 \ - --hash=sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab \ - --hash=sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef \ - --hash=sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95 \ - --hash=sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e \ - --hash=sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595 \ - --hash=sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36 \ - --hash=sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85 \ - --hash=sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd \ - --hash=sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891 \ - --hash=sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9 \ - --hash=sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876 \ - --hash=sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af +ruff==0.4.8 \ + --hash=sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780 \ + --hash=sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268 \ + --hash=sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb \ + --hash=sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed \ + --hash=sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9 \ + --hash=sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a \ + --hash=sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b \ + --hash=sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883 \ + --hash=sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066 \ + --hash=sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199 \ + --hash=sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764 \ + --hash=sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3 \ + --hash=sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45 \ + --hash=sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d \ + --hash=sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa \ + --hash=sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913 \ + --hash=sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc # via -r requirements/dev.in secretstorage==3.3.3 \ --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 # via keyring -setuptools==69.5.1 \ - --hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \ - --hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32 - # via - # -r requirements/base.txt - # nodeenv - # pip-tools shellingham==1.5.4 \ --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \ --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de @@ -451,33 +429,34 @@ tomlkit==0.12.5 \ --hash=sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f \ --hash=sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c # via hatch -trove-classifiers==2024.4.10 \ - --hash=sha256:49f40bb6a746b72a1cba4f8d55ee8252169cda0f70802e3fd24f04b7fb25a492 \ - --hash=sha256:678bd6fcc5218d72e3304e27a608acc9b91e17bd00c3f3d8c968497c843ad98b +trove-classifiers==2024.5.22 \ + --hash=sha256:8a6242bbb5c9ae88d34cf665e816b287d2212973c8777dfaef5ec18d72ac1d03 \ + --hash=sha256:c43ade18704823e4afa3d9db7083294bc4708a5e02afbcefacd0e9d03a7a24ef # via hatchling userpath==1.9.2 \ --hash=sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d \ --hash=sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815 # via hatch -uv==0.1.44 \ - --hash=sha256:012fcfc3789f303ee3ff9f2a6e09bc589710fed7c2dcbad4379832072bad7a95 \ - --hash=sha256:05774eb086b18aad488c3140daa62a235e3f270f62bd3cc4aaaa54eed927cc5b \ - --hash=sha256:26d07edb37e7bfddc3b4e1faa13420e6048ddd974b34fbc1c19fcf9bacef9e5b \ - --hash=sha256:2e5a60af214f42b621aa37ad320253c64f77dbfacafa710dc42d34965c2cd27f \ - --hash=sha256:2f95acffcdac507de9c8f8ed037e529df3ccea274b4453df05df3f331543f5fc \ - --hash=sha256:3eeac3d8be69831430743f3d00f84ddccfbd56b6835bb52d17f97914c9adfdff \ - --hash=sha256:567486ce0ad2f9778782ba6ee19d2b65516c4f4bf2b7b4fc66fc2712cd46c6d0 \ - --hash=sha256:768369a0bbdea8c3a670388ec1b4a11fe5871ef40d84a43844e9b8d97a1c2ca5 \ - --hash=sha256:7983b00d95290dcdea8488fa8ecdfdef5c8e7d3c92c90b8dcf405cc26b707add \ - --hash=sha256:8f90e80e11da409ce88424381f5c91e7f908d6a7eec53ed4ae60c5d76698d126 \ - --hash=sha256:9ef3448111b47ab95874fbf2c5ca8efd52f54de14086079e52b588d037d243f1 \ - --hash=sha256:b04eb0c8dedadfe434f9756bdc1c8a09a75df83884ba4cc7d97985ee819e4f32 \ - --hash=sha256:b076828cef1f1ae1c3b54fa97b9e16b32816acc521ca6ff4a54fd8b16df67eef \ - --hash=sha256:d60e5b77b958c559324882da13ffa642dcd511e6a7eb9b07e7308a6d71e248de \ - --hash=sha256:d82c7338f8bcb0551672e759e4115c035246321059692416ee03ebe08629b913 \ - --hash=sha256:e247dca0d8d42d71032ac99ef3d72a4fcbad4ae3114ef5979878a81a40fed274 \ - --hash=sha256:e8cb1047b8f81ef09e15ec8d1b8dfc371594232e2e4f3ef3acf8991fcda20a57 +uv==0.2.9 \ + --hash=sha256:1942808d1df3fab9482c5ab47484a7a5eb75aacea9b27c2cbbfb60cb826f4c56 \ + --hash=sha256:22deea3d7f83845a54503d1c2d5ff49d28cff109123df415be4496cf1c851b2e \ + --hash=sha256:23cf2e847a0e02e2ddc938c164dbec4878583c61fabb0a1134d00fc9ebf92076 \ + --hash=sha256:290c23ed4d2a16500c685e347326c2137931350f7148e41bb04941a9d90e2d45 \ + --hash=sha256:326d88b12745558eb2b49968c03e088ccab3a8860a7962ee3a0fcfc1289fdb41 \ + --hash=sha256:361368cd38c0d5595374bc22c4733d8c2252c00649158f2168f894a098823c5e \ + --hash=sha256:3a9914fab493978eed5781b3fc13126836472f1a91f1092c07d825150405a42a \ + --hash=sha256:5b0c27f12ec91e8ee6066fd8ee0b3405a791350e8898cdfa99177048c3b364a6 \ + --hash=sha256:6251f4d7d77d29aab625b02a93160ede1ba9edd766918da5064552219561e245 \ + --hash=sha256:692fed655d5cbb457fa2762085bb49f9a74a13ad1608ebfa9b1bd81ed09bdaee \ + --hash=sha256:6b5f4f612c3f2008314fcd2415a1d0c22050d87324476aa8907695723c72483b \ + --hash=sha256:718d3e5e46084c7c6305a20789e27eeea2171a85ef3af37b33324a30b04dedc8 \ + --hash=sha256:8a345a0e4d5b4a31297bb5aea9ef5dd84d91faff1dcb7939e263d4e7b344ae15 \ + --hash=sha256:8ccb4e20606a1cd710573c63b0d79c9ae7d3fbb97d7962efb05344d9b9c83f6b \ + --hash=sha256:92e0af80ab5c40bed88064ff1adc95f587c501ed8371077330703edfafe78661 \ + --hash=sha256:c4d6914fd31213512e75d57ece4efed443aec669fd23ed83c098530a3bf7f39f \ + --hash=sha256:e63daf67b3e6922a58b571a5cf63605e737cb2f34b48973014cc0fd4e8528ecd # via + # -r requirements/testing.txt # hatch # nox virtualenv==20.26.2 \ @@ -487,12 +466,6 @@ virtualenv==20.26.2 \ # hatch # nox # pre-commit -wheel==0.43.0 \ - --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ - --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 - # via - # -r requirements/base.txt - # pip-tools zstandard==0.22.0 \ --hash=sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd \ --hash=sha256:1958100b8a1cc3f27fa21071a55cb2ed32e9e5df4c3c6e661c193437f171cba2 \ diff --git a/requirements/testing.in b/requirements/testing.in new file mode 100644 index 0000000..a266cb8 --- /dev/null +++ b/requirements/testing.in @@ -0,0 +1,6 @@ +-r base.txt + +uv +coverage +pytest +pytest-cov diff --git a/requirements/testing.txt b/requirements/testing.txt new file mode 100644 index 0000000..fd224db --- /dev/null +++ b/requirements/testing.txt @@ -0,0 +1,114 @@ +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via -r requirements/base.txt +coverage==7.5.3 \ + --hash=sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523 \ + --hash=sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f \ + --hash=sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d \ + --hash=sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb \ + --hash=sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0 \ + --hash=sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c \ + --hash=sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98 \ + --hash=sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83 \ + --hash=sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8 \ + --hash=sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7 \ + --hash=sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac \ + --hash=sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84 \ + --hash=sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb \ + --hash=sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3 \ + --hash=sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884 \ + --hash=sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614 \ + --hash=sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd \ + --hash=sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807 \ + --hash=sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd \ + --hash=sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8 \ + --hash=sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc \ + --hash=sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db \ + --hash=sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0 \ + --hash=sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08 \ + --hash=sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232 \ + --hash=sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d \ + --hash=sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a \ + --hash=sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1 \ + --hash=sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286 \ + --hash=sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303 \ + --hash=sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341 \ + --hash=sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84 \ + --hash=sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45 \ + --hash=sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc \ + --hash=sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec \ + --hash=sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd \ + --hash=sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155 \ + --hash=sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52 \ + --hash=sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d \ + --hash=sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485 \ + --hash=sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31 \ + --hash=sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d \ + --hash=sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d \ + --hash=sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d \ + --hash=sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85 \ + --hash=sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce \ + --hash=sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb \ + --hash=sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974 \ + --hash=sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24 \ + --hash=sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56 \ + --hash=sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9 \ + --hash=sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35 + # via + # -r requirements/testing.in + # pytest-cov +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 + # via pytest +packaging==24.0 \ + --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ + --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 + # via + # -r requirements/base.txt + # pip-requirements-parser + # pytest +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via -r requirements/base.txt +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via pytest +pyparsing==3.1.2 \ + --hash=sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad \ + --hash=sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742 + # via + # -r requirements/base.txt + # pip-requirements-parser +pytest==8.2.2 \ + --hash=sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343 \ + --hash=sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977 + # via + # -r requirements/testing.in + # pytest-cov +pytest-cov==5.0.0 \ + --hash=sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652 \ + --hash=sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857 + # via -r requirements/testing.in +uv==0.2.9 \ + --hash=sha256:1942808d1df3fab9482c5ab47484a7a5eb75aacea9b27c2cbbfb60cb826f4c56 \ + --hash=sha256:22deea3d7f83845a54503d1c2d5ff49d28cff109123df415be4496cf1c851b2e \ + --hash=sha256:23cf2e847a0e02e2ddc938c164dbec4878583c61fabb0a1134d00fc9ebf92076 \ + --hash=sha256:290c23ed4d2a16500c685e347326c2137931350f7148e41bb04941a9d90e2d45 \ + --hash=sha256:326d88b12745558eb2b49968c03e088ccab3a8860a7962ee3a0fcfc1289fdb41 \ + --hash=sha256:361368cd38c0d5595374bc22c4733d8c2252c00649158f2168f894a098823c5e \ + --hash=sha256:3a9914fab493978eed5781b3fc13126836472f1a91f1092c07d825150405a42a \ + --hash=sha256:5b0c27f12ec91e8ee6066fd8ee0b3405a791350e8898cdfa99177048c3b364a6 \ + --hash=sha256:6251f4d7d77d29aab625b02a93160ede1ba9edd766918da5064552219561e245 \ + --hash=sha256:692fed655d5cbb457fa2762085bb49f9a74a13ad1608ebfa9b1bd81ed09bdaee \ + --hash=sha256:6b5f4f612c3f2008314fcd2415a1d0c22050d87324476aa8907695723c72483b \ + --hash=sha256:718d3e5e46084c7c6305a20789e27eeea2171a85ef3af37b33324a30b04dedc8 \ + --hash=sha256:8a345a0e4d5b4a31297bb5aea9ef5dd84d91faff1dcb7939e263d4e7b344ae15 \ + --hash=sha256:8ccb4e20606a1cd710573c63b0d79c9ae7d3fbb97d7962efb05344d9b9c83f6b \ + --hash=sha256:92e0af80ab5c40bed88064ff1adc95f587c501ed8371077330703edfafe78661 \ + --hash=sha256:c4d6914fd31213512e75d57ece4efed443aec669fd23ed83c098530a3bf7f39f \ + --hash=sha256:e63daf67b3e6922a58b571a5cf63605e737cb2f34b48973014cc0fd4e8528ecd + # via -r requirements/testing.in diff --git a/src/conftest.py b/src/conftest.py new file mode 100644 index 0000000..a3c9054 --- /dev/null +++ b/src/conftest.py @@ -0,0 +1,9 @@ +def pytest_configure(config): + # Any warnings not ignored here should cause pytest to throw an error. It seems like the error + # setting should go be last in the list, but warnings that match multiple lines will apply the + # last line matched. + config.addinivalue_line('filterwarnings', 'error') + + # This sets the root logger level. Set it to warning by default to keep down the noise on test + # failures. Override like --log-level=info. + config.option.log_level = 'debug' diff --git a/src/reqs/cli.py b/src/reqs/cli.py index 7453b1f..b0beafa 100644 --- a/src/reqs/cli.py +++ b/src/reqs/cli.py @@ -1,3 +1,4 @@ +from collections.abc import Iterable as Iter import logging from os import environ from pathlib import Path @@ -6,7 +7,7 @@ import click from . import config -from .utils import pip, pip_sync, pipx_install, reqs_compile +from .utils import DepHandler, pip, pip_sync, pipx_install log = logging.getLogger() @@ -21,9 +22,8 @@ def conf_prep() -> config.Config: return conf -def _compile(force: bool, conf: config.Config): - for dep in conf.depends: - reqs_compile(force, conf.reqs_dpath, dep.fname, *dep.depends_on) +def compile_all(force: bool, conf: config.Config, upgrade_packages: Iter[str] = ()): + DepHandler(conf.reqs_dpath).compile_all(force, upgrade_packages) @click.group() @@ -38,7 +38,7 @@ def reqs(quiet: bool, verbose: bool): @reqs.command() @click.option('--uv/--no-uv', 'use_uv', default=True) def bootstrap(use_uv: bool): - """Upgrade pip & install pip-tools""" + """Install uv (default) or pip-tools to compile .in files""" if use_uv: log.info('Installing and/or upgrading uv') pip('install', '--quiet', '-U', 'uv') @@ -62,7 +62,15 @@ def _config(): def compile(force: bool): """Compile .in to .txt when needed""" conf = conf_prep() - _compile(force, conf) + compile_all(force, conf) + + +@reqs.command() +@click.argument('packages', nargs=-1) +def upgrade(packages: list[str]): + """Upgrade package(s) to latest version""" + conf = conf_prep() + compile_all(True, conf, packages) @reqs.command() @@ -74,7 +82,7 @@ def sync(req_fname: str, compile: bool, force: bool): conf = conf_prep() if compile: - _compile(force, conf) + compile_all(force, conf) if venv_path := environ.get('VIRTUAL_ENV'): # Install reqs into active venv diff --git a/src/reqs/config.py b/src/reqs/config.py index dd4944e..952e2d0 100644 --- a/src/reqs/config.py +++ b/src/reqs/config.py @@ -1,14 +1,6 @@ import dataclasses as dc from pathlib import Path import tomllib -import warnings - - -DEFAULT_DEPENDS = { - 'base.in': [], - 'dev.in': ['base.txt'], - 'ci.in': ['dev.txt'], -} def find_upwards(d: Path, filename: str): @@ -32,41 +24,11 @@ def deep_get(d: dict, dotted_path: str, default=None): return d -@dc.dataclass -class Depends: - fname: str - depends_on: list[str] - - def __post_init__(self): - if self.depends_on in ('', False): - self.depends_on = [] - elif isinstance(self.depends_on, str): - self.depends_on = [self.depends_on] - - -def default_depends(reqs_dpath: Path): - in_fpaths = sorted(reqs_dpath.glob('*.in')) - names = {path.name for path in in_fpaths} - - if names == {'base.in', 'dev.in', 'ci.in'}: - return DEFAULT_DEPENDS - - if len(in_fpaths) > 1: - # TODO: this warning is thrown even when dependencies have been specified - warnings.warn( - 'More than one .in file and no dependencies specified in the config.', - stacklevel=3, - ) - - return {path.name: '' for path in in_fpaths} - - @dc.dataclass class Config: pkg_dpath: Path reqs_dpath: Path sync_pipx: bool - depends: list[Depends] def as_dict(self): return dc.asdict(self) @@ -105,10 +67,12 @@ def load(start_at: Path): reqs_dpath = pkg_dpath.joinpath( deep_get(proj_config, 'tool.reqs.dpath', 'requirements'), ) - depends = deep_get(proj_config, 'tool.reqs.depends', default_depends(reqs_dpath)) + + if deep_get(proj_config, 'tool.reqs.depends') is not None: + raise RuntimeError('reqs.depends is no longer needed, please remove.') + return Config( pkg_dpath=pkg_dpath, reqs_dpath=reqs_dpath, sync_pipx=deep_get(proj_config, 'tool.reqs.sync_pipx', False), - depends=[Depends(k, v) for k, v in depends.items()], ) diff --git a/src/reqs/tests/pkg1/requirements/base.in b/src/reqs/libs/__init__.py similarity index 100% rename from src/reqs/tests/pkg1/requirements/base.in rename to src/reqs/libs/__init__.py diff --git a/src/reqs/libs/testing.py b/src/reqs/libs/testing.py new file mode 100644 index 0000000..eea437e --- /dev/null +++ b/src/reqs/libs/testing.py @@ -0,0 +1,83 @@ +from contextlib import contextmanager +import os +from pathlib import Path +from unittest import mock + +from click.testing import CliRunner, Result + +import reqs.tests + + +tests_dpath = Path(reqs.tests.__file__).parent + + +def mock_patch_obj(*args, **kwargs): + kwargs.setdefault('autospec', True) + kwargs.setdefault('spec_set', True) + return mock.patch.object(*args, **kwargs) + + +def mock_patch(*args, **kwargs): + kwargs.setdefault('autospec', True) + kwargs.setdefault('spec_set', True) + return mock.patch(*args, **kwargs) + + +def logs(caplog): + return [rec.message for rec in caplog.records] + + +@contextmanager +def chdir(path: Path): + cwd = Path.cwd() + try: + os.chdir(path) + yield + finally: + os.chdir(cwd) + + +class Package: + def __init__(self, pkg_name_or_root: str | Path): + if Path(pkg_name_or_root).is_absolute(): + self.dpath: Path = pkg_name_or_root + else: + self.dpath: Path = tests_dpath / pkg_name_or_root + self.reqs_dpath = self.dpath / 'requirements' + + def invoke(self, cli, *args, pkg_chdir=None, **env) -> Result: + runner = CliRunner(mix_stderr=False) + + from_dpath = self.dpath / pkg_chdir if pkg_chdir else self.dpath + with chdir(from_dpath): + result = runner.invoke(cli.reqs, args, env=env, catch_exceptions=False) + + assert result.exit_code == 0, (result.stdout, result.stderr) + return result + + def txt_reqs(self) -> list[Path]: + return sorted(self.reqs_dpath.glob('*.txt')) + + def txt_names(self): + return [p.name for p in self.txt_reqs()] + + def txt_unlink(self, keep=None): + keep = keep or () + + for txt_fpath in self.txt_reqs(): + if txt_fpath.name in keep: + continue + txt_fpath.unlink() + + def reqs_fpath(self, fname: str): + return self.reqs_dpath / fname + + def reqs_text(self, fname: str): + return self.reqs_dpath.joinpath(fname).read_text() + + def reqs_create(self, fname: str, *lines): + reqs_fpath = self.reqs_fpath(fname) + self.reqs_dpath.mkdir(exist_ok=True) + self.dpath.joinpath('pyproject.toml').touch() + + reqs_fpath.write_text('\n'.join(lines)) diff --git a/src/reqs/tests/pkg-upgrade/pyproject.toml b/src/reqs/tests/pkg-upgrade/pyproject.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/reqs/tests/pkg-upgrade/requirements/alpha.in b/src/reqs/tests/pkg-upgrade/requirements/alpha.in new file mode 100644 index 0000000..1313f97 --- /dev/null +++ b/src/reqs/tests/pkg-upgrade/requirements/alpha.in @@ -0,0 +1,4 @@ +-r bravo.txt +-r charlie.txt + +iniconfig == 1.1.1 diff --git a/src/reqs/tests/pkg-upgrade/requirements/bravo.in b/src/reqs/tests/pkg-upgrade/requirements/bravo.in new file mode 100644 index 0000000..ddf1fe0 --- /dev/null +++ b/src/reqs/tests/pkg-upgrade/requirements/bravo.in @@ -0,0 +1,3 @@ +-r charlie.txt + +pluggy == 1.4.0 diff --git a/src/reqs/tests/pkg-upgrade/requirements/charlie.in b/src/reqs/tests/pkg-upgrade/requirements/charlie.in new file mode 100644 index 0000000..70fb4e0 --- /dev/null +++ b/src/reqs/tests/pkg-upgrade/requirements/charlie.in @@ -0,0 +1,2 @@ +uv +pluggy diff --git a/src/reqs/tests/pkg1/requirements/alpha.in b/src/reqs/tests/pkg1/requirements/alpha.in new file mode 100644 index 0000000..f6dd862 --- /dev/null +++ b/src/reqs/tests/pkg1/requirements/alpha.in @@ -0,0 +1,2 @@ +-c bravo.txt +-r charlie.txt diff --git a/src/reqs/tests/pkg1/requirements/bravo.in b/src/reqs/tests/pkg1/requirements/bravo.in new file mode 100644 index 0000000..dbfd2fd --- /dev/null +++ b/src/reqs/tests/pkg1/requirements/bravo.in @@ -0,0 +1 @@ +-r charlie.txt diff --git a/src/reqs/tests/pkg1/requirements/charlie.in b/src/reqs/tests/pkg1/requirements/charlie.in new file mode 100644 index 0000000..60cc5e6 --- /dev/null +++ b/src/reqs/tests/pkg1/requirements/charlie.in @@ -0,0 +1 @@ +uv diff --git a/src/reqs/tests/pkg1/requirements/ci.in b/src/reqs/tests/pkg1/requirements/ci.in deleted file mode 100644 index 668e661..0000000 --- a/src/reqs/tests/pkg1/requirements/ci.in +++ /dev/null @@ -1 +0,0 @@ --c base.txt diff --git a/src/reqs/tests/pkg1/requirements/dev.in b/src/reqs/tests/pkg1/requirements/dev.in deleted file mode 100644 index b82eab9..0000000 --- a/src/reqs/tests/pkg1/requirements/dev.in +++ /dev/null @@ -1,2 +0,0 @@ --r base.txt --r ci.txt diff --git a/src/reqs/tests/pkg2/pyproject.toml b/src/reqs/tests/pkg2/pyproject.toml index 0aa85dd..bbec7b8 100644 --- a/src/reqs/tests/pkg2/pyproject.toml +++ b/src/reqs/tests/pkg2/pyproject.toml @@ -1,7 +1,3 @@ [tool.reqs] dpath = 'reqs' sync_pipx = true - -[tool.reqs.depends] -'common.in' = '' -'dev.in' = 'common.txt' diff --git a/src/reqs/tests/pkg4/requirements/base.in b/src/reqs/tests/pkg4/requirements/base.in index e69de29..60cc5e6 100644 --- a/src/reqs/tests/pkg4/requirements/base.in +++ b/src/reqs/tests/pkg4/requirements/base.in @@ -0,0 +1 @@ +uv diff --git a/src/reqs/tests/pkg4/requirements/foo.in b/src/reqs/tests/pkg4/requirements/foo.in index e69de29..60cc5e6 100644 --- a/src/reqs/tests/pkg4/requirements/foo.in +++ b/src/reqs/tests/pkg4/requirements/foo.in @@ -0,0 +1 @@ +uv diff --git a/src/reqs/tests/pkg5/pyproject.toml b/src/reqs/tests/pkg5/pyproject.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/reqs/tests/pkg5/requirements/alpha.in b/src/reqs/tests/pkg5/requirements/alpha.in new file mode 100644 index 0000000..f6dd862 --- /dev/null +++ b/src/reqs/tests/pkg5/requirements/alpha.in @@ -0,0 +1,2 @@ +-c bravo.txt +-r charlie.txt diff --git a/src/reqs/tests/pkg5/requirements/bravo.in b/src/reqs/tests/pkg5/requirements/bravo.in new file mode 100644 index 0000000..dbfd2fd --- /dev/null +++ b/src/reqs/tests/pkg5/requirements/bravo.in @@ -0,0 +1 @@ +-r charlie.txt diff --git a/src/reqs/tests/test_cli.py b/src/reqs/tests/test_cli.py index 7d64f7b..8c716c6 100644 --- a/src/reqs/tests/test_cli.py +++ b/src/reqs/tests/test_cli.py @@ -1,28 +1,16 @@ from contextlib import contextmanager -from dataclasses import dataclass import logging import os from pathlib import Path from unittest import mock -from click.testing import CliRunner, Result - from reqs import cli +from reqs.libs.testing import Package pkgs_dpath = Path(__file__).parent -@contextmanager -def chdir(path: Path): - cwd = Path.cwd() - try: - os.chdir(path) - yield - finally: - os.chdir(cwd) - - @contextmanager def env_del(*keys): current_env = dict(os.environ) @@ -33,99 +21,100 @@ def env_del(*keys): os.environ[key] = current_env[key] -@dataclass -class Invoke: - result: Result - pkg_dpath: Path - - -def invoke(pkg_name, *args, pkg_chdir=None, **env) -> Result: - runner = CliRunner(mix_stderr=False) - - pkg_dpath = pkgs_dpath / pkg_name - from_dpath = pkg_dpath / pkg_chdir if pkg_chdir else pkg_dpath - with chdir(from_dpath): - result = runner.invoke(cli.reqs, args, env=env, catch_exceptions=False) - - assert result.exit_code == 0, (result.stdout, result.stderr) - return Invoke(result, pkg_dpath) - - class TestCLI: - @mock.patch.object(cli, 'reqs_compile') - def test_compile(self, m_reqs_compile): - inv: Invoke = invoke('pkg1', 'compile') - reqs_dpath = inv.pkg_dpath / 'requirements' - - assert not inv.result.stderr - assert not inv.result.stdout - assert m_reqs_compile.mock_calls == [ - mock.call(False, reqs_dpath, 'base.in'), - mock.call(False, reqs_dpath, 'dev.in', 'base.txt'), - mock.call(False, reqs_dpath, 'ci.in', 'dev.txt'), - ] - - @mock.patch.object(cli, 'reqs_compile') - def test_compile_force(self, m_reqs_compile): - inv: Invoke = invoke('pkg1', 'compile', '--force') - reqs_dpath = inv.pkg_dpath / 'requirements' - - assert m_reqs_compile.mock_calls == [ - mock.call(True, reqs_dpath, 'base.in'), - mock.call(True, reqs_dpath, 'dev.in', 'base.txt'), - mock.call(True, reqs_dpath, 'ci.in', 'dev.txt'), - ] - - @mock.patch.object(cli, 'reqs_compile') - def test_path_calc_from_pkg_directory(self, m_reqs_compile): + def test_compile_no_mocking(self): + package = Package('pkg1') + package.txt_unlink() + assert len(package.txt_reqs()) == 0 + + package.invoke(cli, 'compile') + assert len(package.txt_reqs()) == 3 + + def test_upgrade(self, tmp_path): + package = Package(tmp_path) + # Packages chosen b/c pytest uses them and the pip cache will already have the + # package downloaded. Also b/c they currently have no transitive deps. + package.reqs_create('base.in', 'pluggy == 1.4.0') + package.reqs_create('dev.in', '-r base.txt', 'iniconfig == 1.1.1') + + package.invoke(cli, 'compile') + assert 'pluggy==1.4.0' in package.reqs_text('dev.txt') + assert 'iniconfig==1.1.1' in package.reqs_text('dev.txt') + + package.reqs_create('base.in', 'pluggy') + package.reqs_create('dev.in', '-r base.txt', 'iniconfig') + + package.invoke(cli, '--verbose', 'upgrade', 'pluggy', 'iniconfig') + dev_reqs = package.reqs_text('dev.txt') + assert 'pluggy==1.4.0' not in dev_reqs and 'pluggy==' in dev_reqs + assert 'iniconfig==1.1.1' not in dev_reqs and 'iniconfig==' in dev_reqs + + @mock.patch.object(cli, 'compile_all') + @mock.patch.object(cli, 'conf_prep') + def test_compile_force(self, m_conf_prep, m_compile_all): + Package('pkg1').invoke(cli, 'compile') + m_compile_all.assert_called_once_with( + False, + m_conf_prep.return_value, + ) + + Package('pkg1').invoke(cli, 'compile', '--force') + m_compile_all.assert_called_with( + True, + m_conf_prep.return_value, + ) + + @mock.patch.object(cli, 'compile_all') + def test_path_calc_from_pkg_directory(self, m_compile_all): """Ensure relative path calculation is done from package directory and not cwd""" - inv: Invoke = invoke('pkg1', 'compile', '--force', pkg_chdir='foo') + result = Package('pkg1').invoke(cli, 'compile', '--force', pkg_chdir='foo') - assert not inv.result.stderr - assert not inv.result.stdout + assert not result.stderr + assert not result.stdout @mock.patch.object(cli, 'pip') @mock.patch.object(cli, 'pip_sync') - @mock.patch.object(cli, 'reqs_compile') - def test_sync(self, m_reqs_compile, m_pip_sync, m_pip, caplog): + @mock.patch.object(cli, 'compile_all') + def test_sync(self, m_compile_all, m_pip_sync, m_pip, caplog): caplog.set_level(logging.INFO) - inv: Invoke = invoke('pkg1', 'sync', pkg_chdir='foo', VIRTUAL_ENV='pkg1') - reqs_dpath = inv.pkg_dpath / 'requirements' + package = Package('pkg1') + result = package.invoke(cli, 'sync', pkg_chdir='foo', VIRTUAL_ENV='pkg1') - assert not inv.result.stderr + assert not result.stderr assert [rec.message for rec in caplog.records] == [ 'Installing requirements/dev.txt to venv @ pkg1', ] - assert m_reqs_compile.mock_calls == [ - mock.call(False, reqs_dpath, 'base.in'), - mock.call(False, reqs_dpath, 'dev.in', 'base.txt'), - mock.call(False, reqs_dpath, 'ci.in', 'dev.txt'), - ] + m_compile_all.assert_called_once_with( + False, + mock.ANY, + ) assert m_pip_sync.mock_calls == [ - mock.call('--quiet', reqs_dpath / 'dev.txt'), + mock.call('--quiet', package.reqs_dpath / 'dev.txt'), ] assert m_pip.mock_calls == [ - mock.call('install', '--quiet', '-e', inv.pkg_dpath), + mock.call('install', '--quiet', '-e', package.dpath), ] @mock.patch.object(cli, 'pipx_install') @mock.patch.object(cli, 'pip') @mock.patch.object(cli, 'pip_sync') - @mock.patch.object(cli, 'reqs_compile') + @mock.patch.object(cli, 'compile_all') def test_sync_no_venv_no_compile_with_pipx( self, - m_reqs_compile, + m_compile_all, m_pip_sync, m_pip, m_pipx_install, ): + package = Package('pkg2') + with env_del('VIRTUAL_ENV'): - inv: Invoke = invoke('pkg2', 'sync', '--no-compile') + result = package.invoke(cli, 'sync', '--no-compile') - assert not inv.result.stderr - assert not inv.result.stdout - assert m_reqs_compile.mock_calls == [] + assert not result.stderr + assert not result.stdout + assert m_compile_all.mock_calls == [] assert m_pip_sync.mock_calls == [] assert m_pip.mock_calls == [] - assert m_pipx_install.mock_calls == [mock.call('install', '--force', '-e', inv.pkg_dpath)] + assert m_pipx_install.mock_calls == [mock.call('install', '--force', '-e', package.dpath)] diff --git a/src/reqs/tests/test_config.py b/src/reqs/tests/test_config.py deleted file mode 100644 index 7bf4c57..0000000 --- a/src/reqs/tests/test_config.py +++ /dev/null @@ -1,49 +0,0 @@ -from pathlib import Path - -import pytest - -from reqs import config - - -tests_dpath = Path(__file__).parent - - -def load(*start_at): - return config.load(tests_dpath.joinpath(*start_at)) - - -class TestConfig: - def test_depends_default(self): - c: config.Config = load('pkg1') - assert c.pkg_dpath == tests_dpath.joinpath('pkg1') - assert c.reqs_dpath == tests_dpath.joinpath('pkg1', 'requirements') - assert not c.sync_pipx - assert c.depends == [ - config.Depends('base.in', []), - config.Depends('dev.in', ['base.txt']), - config.Depends('ci.in', ['dev.txt']), - ] - - def test_depends_explicit(self): - c: config.Config = load('pkg2') - assert c.reqs_dpath == tests_dpath.joinpath('pkg2', 'reqs') - assert c.sync_pipx - assert c.depends == [ - config.Depends('common.in', []), - config.Depends('dev.in', ['common.txt']), - ] - - def test_base_only(self): - c: config.Config = load('pkg3') - assert c.depends == [ - config.Depends('base.in', []), - ] - - def test_warning(self): - with pytest.warns(UserWarning, match=r'More than one \.in file'): - c: config.Config = load('pkg4') - - assert c.depends == [ - config.Depends('base.in', []), - config.Depends('foo.in', []), - ] diff --git a/src/reqs/tests/test_utils.py b/src/reqs/tests/test_utils.py new file mode 100644 index 0000000..6b4620e --- /dev/null +++ b/src/reqs/tests/test_utils.py @@ -0,0 +1,140 @@ +import logging +import os +from pathlib import Path +import time + +from reqs.utils import Dep, DepHandler + +from ..libs.testing import Package as _Package +from ..libs.testing import logs + + +tests_dpath = Path(__file__).parent + + +class Package(_Package): + def dep_handler(self, keep=None): + self.txt_unlink(keep) + return DepHandler(self.reqs_dpath) + + def dep_from(self, fname): + return Dep(self.reqs_fpath(fname)) + + def touch_shift(self, fname, *, shift=1): + path = self.reqs_fpath(fname) + + path.touch() + + to_time = time.time() + shift + + # Apply the new access and modification times using os.utime + os.utime(path, (to_time, to_time)) + + +class TestDep: + def test_needs(self): + pkg1 = Package('pkg1') + dep = pkg1.dep_from('charlie.in') + assert not dep.needs + + dep = pkg1.dep_from('bravo.in') + assert dep.needs == [pkg1.reqs_fpath('charlie.txt')] + + dep = pkg1.dep_from('alpha.in') + assert dep.needs == [pkg1.reqs_fpath('bravo.txt'), pkg1.reqs_fpath('charlie.txt')] + + def test_txt_stale(self): + pkg1 = Package('pkg1') + pkg1.txt_unlink() + + # No .txt file + dep = pkg1.dep_from('charlie.in') + assert dep.txt_stale() + + # .txt file newer + dep.path_txt.touch() + assert not dep.txt_stale() + + # .txt file older + dep.path.touch() + assert dep.txt_stale() + + # No .txt file + dep = pkg1.dep_from('bravo.in') + pkg1.touch_shift('bravo.txt') + + # bravo's txt file newer than its .in + assert not dep.txt_stale() + + pkg1.touch_shift('charlie.txt') + + # bravo's txt file is now older than charlie.txt + assert dep.txt_stale() + + # No .txt file + dep = pkg1.dep_from('alpha.in') + pkg1.touch_shift('alpha.txt') + + # bravo's txt file is older than alpha's + pkg1.touch_shift('charlie.txt', shift=-1) + assert not dep.txt_stale() + + # charlies's txt file is newer than alpha's, making alpha's stale + pkg1.touch_shift('charlie.txt') + assert dep.txt_stale() + + +class TestDepHandler: + def test_no_files(self, caplog): + dh = Package('pkg2').dep_handler() + dh.compile_all() + assert caplog.records[0].message == f'No .in files found at: {dh.reqs_dpath}' + + def test_files_with_no_options(self): + pkg = Package('pkg4') + dh = pkg.dep_handler() + dh.compile_all() + assert pkg.txt_names() == ['base.txt', 'foo.txt'] + + def test_dependent(self): + pkg = Package('pkg1') + pkg.dep_handler().compile_all() + assert '# -r requirements/charlie.txt' in pkg.reqs_text('alpha.txt') + assert '# -c requirements/bravo.txt' in pkg.reqs_text('alpha.txt') + assert 'via -r requirements/charlie.txt' in pkg.reqs_text('bravo.txt') + assert 'via -r requirements/charlie.in' in pkg.reqs_text('charlie.txt') + + def test_transient_deps_modified(self, caplog): + pkg = Package('pkg1') + dh = pkg.dep_handler() + dh.compile_all(force=True) + + pkg.touch_shift('charlie.in') + caplog.clear() + caplog.set_level(logging.INFO) + dh.compile_all() + + assert logs(caplog) == [ + 'compiling: charlie.in', + 'compiling: bravo.in', + 'compiling: alpha.in', + ] + + assert '# -r requirements/charlie.txt' in pkg.reqs_text('alpha.txt') + assert 'via -r requirements/charlie.txt' in pkg.reqs_text('bravo.txt') + assert 'via -r requirements/charlie.in' in pkg.reqs_text('charlie.txt') + + def test_txt_only_dep(self, caplog): + caplog.set_level(logging.INFO) + + pkg = Package('pkg5') + dh = pkg.dep_handler(keep='charlie.txt') + dh.compile_all(force=True) + + assert logs(caplog) == [ + 'compiling: bravo.in', + 'compiling: alpha.in', + ] + + assert '# -r requirements/charlie.txt' in pkg.reqs_text('alpha.txt') + assert 'via -r requirements/charlie.txt' in pkg.reqs_text('bravo.txt') diff --git a/src/reqs/utils.py b/src/reqs/utils.py index 3276f7e..91d5146 100644 --- a/src/reqs/utils.py +++ b/src/reqs/utils.py @@ -1,11 +1,16 @@ +from collections.abc import Iterable as Iter +from dataclasses import dataclass import logging +import os from os import environ from pathlib import Path import shlex import subprocess +from pip_requirements_parser import RequirementsFile -log = logging.getLogger(__name__) + +log = logging.getLogger() def run(*args, **kwargs): @@ -72,32 +77,117 @@ def pipx_install(cmd, *args, **kwargs): run('pipx', 'install', *args, **kwargs) -def reqs_stale(txt_fpath: Path, dep_fpaths: list[Path]): - if not txt_fpath.exists(): - return True - - return any(txt_fpath.stat().st_mtime < dep_fpath.stat().st_mtime for dep_fpath in dep_fpaths) - - -def reqs_compile(force: bool, reqs_dpath: Path, in_fname: str, *dep_fnames: list[str]): - in_fpath: Path = reqs_dpath / in_fname - txt_fpath: Path = in_fpath.with_suffix('.txt') +@dataclass +class Dep: + path: Path + compiled: bool = False + needs: list[Path] = None - dep_fpaths: list[Path] = [in_fpath] - dep_fpaths.extend(reqs_dpath / fname for fname in dep_fnames) + @property + def path_txt(self): + return self.path.with_suffix('.txt') - if force or reqs_stale(txt_fpath, dep_fpaths): - print(f'Compiling: {in_fname}') - pip_compile( - '--quiet', - '--strip-extras', - '--annotate', - '--generate-hashes', - '--no-header', - '--output-file', - txt_fpath, - in_fpath, + def _opt_line_needs(self, opt_line): + yield from ( + *opt_line.options.get('constraints', ()), + *opt_line.options.get('requirements', ()), ) - return - print(f'Up-to-date: {txt_fpath.name}') + def __post_init__(self): + req_file = RequirementsFile.from_file(self.path) + needs = [] + for opt_line in req_file.options: + needs.extend(self._opt_line_needs(opt_line)) + + self.needs = [ + self.path.parent.joinpath(need_path) if not Path(need_path).is_absolute() else need_path + for need_path in needs + ] + if self.path.suffix == '.txt': + self.compiled = True + + def txt_stale(self): + txt_fpath = self.path_txt + if not txt_fpath.exists(): + return True + + txt_mtime = txt_fpath.stat().st_mtime + if self.path.stat().st_mtime >= txt_mtime: + return True + + if not self.needs: + return + + # If our txt file was modified before any of our needs then it has become stale. + if txt_mtime <= max(need.stat().st_mtime for need in self.needs): + return True + + def compile(self, force: bool, upgrade_packages: Iter[str]): + if self.compiled: + log.debug('already compiled: %s', self.path) + return + + if force or self.txt_stale(): + log.info('compiling: %s', self.path.name) + + extra_args = [] + for pkg in upgrade_packages: + extra_args.append('--upgrade-package') + extra_args.append(pkg) + + pip_compile( + '--quiet', + '--strip-extras', + '--annotate', + '--generate-hashes', + '--no-header', + '--output-file', + self.path.with_suffix('.txt'), + self.path, + *extra_args, + ) + else: + log.info('already current: %s', self.path.name) + + self.compiled = True + + +class DepHandler: + def __init__(self, reqs_dpath: Path): + self.reqs_dpath: Path = reqs_dpath + + def compile_all(self, force: bool = False, upgrade_packages: Iter[str] = ()): + reqs_files: dict[Path, Dep] = {p: Dep(p) for p in sorted(self.reqs_dpath.glob('*.in'))} + + if not reqs_files: + log.warning('No .in files found at: %s', self.reqs_dpath) + return + + os.chdir(self.reqs_dpath.parent) + for dep in list(reqs_files.values()): + self._compile(reqs_files, dep, force, upgrade_packages) + + def _compile( + self, + reqs_files: dict[Path, Dep], + dep: Dep, + force: bool, + upgrade_packages: Iter[str], + ): + for needs_fpath in dep.needs: + # TODO: I don't know that this is needed + needs_in_fpath = needs_fpath.with_suffix('.in') + if needs_fpath.suffix == '.txt' and needs_in_fpath.exists(): + needs_fpath = needs_in_fpath + + if not needs_fpath.exists(): + raise RuntimeError( + f'{dep.path.relative_to(self.reqs_dpath)} references a non-existant path:' + f' {needs_fpath.relative_to(self.reqs_dpath)}', + ) + + need_dep: Dep = reqs_files.get(needs_fpath) or Dep(needs_fpath) + reqs_files[needs_fpath] = need_dep + self._compile(reqs_files, need_dep, force, upgrade_packages) + + dep.compile(force, upgrade_packages)