diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index befe5e985..e1bd53b4f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -9,6 +9,29 @@ permissions: checks: write # for coveralls jobs: + lint_and_type: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install poetry + run: pipx install poetry + + - name: setup python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + cache: 'poetry' + + - name: install despondencies + run: poetry install --with dev + + - name: flake it + run: poetry run flake8 . + + - name: type-check + run: poetry run mypy trove + run_tests: strategy: fail-fast: false @@ -60,9 +83,6 @@ jobs: - name: install despondencies run: poetry install --with dev - - name: flake it - run: poetry run flake8 . - - name: run tests run: | poetry run coverage run -m pytest --create-db -x diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..7ef174a9d --- /dev/null +++ b/mypy.ini @@ -0,0 +1,77 @@ +[mypy] +python_version = 3.13 +plugins = mypy_django_plugin.main + +# display options +show_column_numbers = True +pretty = True + +# start with an ideal: enable strict type-checking, then loosen in module-specific config +# see https://mypy.readthedocs.io/en/stable/existing_code.html#introduce-stricter-options +strict = True +## BEGIN possible loosenings from `strict`: +# disallow_subclassing_any = False +# warn_unused_configs = False +# warn_redundant_casts = False +# warn_unused_ignores = False +# strict_equality = False +# strict_concatenate = False +# check_untyped_defs = False +# disallow_untyped_decorators = False +# disallow_any_generics = False +# disallow_untyped_calls = False +# disallow_incomplete_defs = False +# disallow_untyped_defs = False +# no_implicit_reexport = False +# warn_return_any = False +## END loosenings of `strict` + +# prefer types that can be understood by reading code in only one place +local_partial_types = True +# avoid easily-avoidable dead code +warn_unreachable = True +# prefer direct imports +implicit_reexport = False + +# got untyped dependencies -- this is fine +ignore_missing_imports = True +disable_error_code = import-untyped,import-not-found + +### +# plugin config +[mypy.plugins.django-stubs] +django_settings_module = project.settings + +### +# module-specific config + +## sharev2 code; largely unannotated +[mypy-share.*,api.*,project.*,osf_oauth2_adapter.*,manage] +# loosen strict: +disallow_subclassing_any = False +disallow_untyped_decorators = False +disallow_any_generics = False +disallow_untyped_calls = False +disallow_incomplete_defs = False +disallow_untyped_defs = False +warn_return_any = False + +## django migrations are whatever +[mypy-*.migrations.*] +strict = False +#disable_error_code = var-annotated,import-untyped,import-not-found +disallow_subclassing_any = False + +## tests are looser +[mypy-tests.*] +disallow_untyped_defs = False + +## trove code (trying to be) well-annotated (but still uses django...) +[mypy-trove.*] +disallow_subclassing_any = False +disallow_untyped_decorators = False +disallow_any_generics = False +warn_return_any = False +[mypy-trove.views.*] +disallow_untyped_defs = False +disallow_incomplete_defs = False diff --git a/poetry.lock b/poetry.lock index 0e2c5afee..a4aa4e488 100644 --- a/poetry.lock +++ b/poetry.lock @@ -166,14 +166,14 @@ zstd = ["zstandard (==0.22.0)"] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, + {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, ] [[package]] @@ -259,116 +259,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] [[package]] name = "click" -version = "8.1.8" +version = "8.2.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] [package.dependencies] @@ -391,14 +391,14 @@ click = ">=7" [[package]] name = "click-plugins" -version = "1.1.1" +version = "1.1.1.2" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, - {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, + {file = "click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6"}, + {file = "click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261"}, ] [package.dependencies] @@ -459,68 +459,66 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] [[package]] name = "coverage" -version = "5.5" +version = "6.5.0" description = "Code coverage measurement for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] [package.extras] -toml = ["toml"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "coveralls" @@ -559,60 +557,62 @@ dev = ["polib"] [[package]] name = "cryptography" -version = "44.0.2" +version = "45.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["main"] files = [ - {file = "cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a"}, - {file = "cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308"}, - {file = "cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688"}, - {file = "cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7"}, - {file = "cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79"}, - {file = "cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa"}, - {file = "cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9"}, - {file = "cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23"}, - {file = "cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922"}, - {file = "cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4"}, - {file = "cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5"}, - {file = "cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6"}, - {file = "cryptography-44.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb"}, - {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41"}, - {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562"}, - {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5"}, - {file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa"}, - {file = "cryptography-44.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d"}, - {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d"}, - {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471"}, - {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615"}, - {file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390"}, - {file = "cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0"}, + {file = "cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999"}, + {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750"}, + {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2"}, + {file = "cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257"}, + {file = "cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8"}, + {file = "cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6"}, + {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872"}, + {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4"}, + {file = "cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97"}, + {file = "cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d"}, + {file = "cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57"}, ] [package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} +cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""] +pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==45.0.4)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -758,6 +758,45 @@ jwcrypto = ">=1.5.0" oauthlib = ">=3.2.2" requests = ">=2.13.0" +[[package]] +name = "django-stubs" +version = "5.2.1" +description = "Mypy stubs for Django" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "django_stubs-5.2.1-py3-none-any.whl", hash = "sha256:c0e170d70329c27e737a5b80c5518fb6161d0c4792321d11a4a93dcda120f4ef"}, + {file = "django_stubs-5.2.1.tar.gz", hash = "sha256:e58260958e58f7b6a8da6bba56d6b31d16c0414079a4aa6baa01c668bd08d39d"}, +] + +[package.dependencies] +django = "*" +django-stubs-ext = ">=5.2.1" +types-pyyaml = "*" +typing-extensions = ">=4.11.0" + +[package.extras] +compatible-mypy = ["mypy (>=1.13,<1.17)"] +oracle = ["oracledb"] +redis = ["redis", "types-redis"] + +[[package]] +name = "django-stubs-ext" +version = "5.2.1" +description = "Monkey-patching and extensions for django-stubs" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "django_stubs_ext-5.2.1-py3-none-any.whl", hash = "sha256:98fb0646f1a1ef07708eec5f6f7d27523f12c0c8714abae8db981571ff957588"}, + {file = "django_stubs_ext-5.2.1.tar.gz", hash = "sha256:fc0582cb3289306c43ce4a0a15af86922ce1dbec3c19eab80980ee70c04e0392"}, +] + +[package.dependencies] +django = "*" +typing-extensions = "*" + [[package]] name = "django-timezone-field" version = "7.1" @@ -978,86 +1017,67 @@ test = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "cove [[package]] name = "greenlet" -version = "3.1.1" +version = "3.2.3" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["deploy"] markers = "platform_python_implementation == \"CPython\"" files = [ - {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, - {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, - {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, - {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, - {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, - {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, - {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, - {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, - {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, - {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, - {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, - {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, - {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, - {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, - {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, - {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, - {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, + {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"}, + {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"}, + {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"}, + {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"}, + {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"}, + {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"}, + {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"}, + {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"}, + {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"}, + {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"}, + {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"}, + {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"}, + {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"}, + {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"}, ] [package.extras] @@ -1066,16 +1086,19 @@ test = ["objgraph", "psutil"] [[package]] name = "idna" -version = "2.10" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "inflection" version = "0.5.1" @@ -1336,6 +1359,72 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mypy" +version = "1.16.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, + {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, + {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, + {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, + {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, + {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, + {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, + {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, + {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, + {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, + {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, + {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, + {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + [[package]] name = "newrelic" version = "10.7.0" @@ -1380,14 +1469,14 @@ infinite-tracing = ["grpcio", "protobuf"] [[package]] name = "oauthlib" -version = "3.2.2" +version = "3.3.1" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, + {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, + {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, ] [package.extras] @@ -1397,31 +1486,43 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "primitive-metadata" @@ -1437,14 +1538,14 @@ files = [ [[package]] name = "prompt-toolkit" -version = "3.0.50" +version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, - {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] @@ -1758,14 +1859,14 @@ unleash = ["UnleashClient (>=6.0.1)"] [[package]] name = "setuptools" -version = "78.1.0" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["deploy"] files = [ - {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, - {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] @@ -1817,16 +1918,28 @@ files = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250516" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, + {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, +] + [[package]] name = "typing-extensions" -version = "4.13.0" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, - {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, + {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, + {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, ] [[package]] @@ -1843,20 +1956,21 @@ files = [ [[package]] name = "urllib3" -version = "1.26.20" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, - {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uwsgi" @@ -1970,4 +2084,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<3.14" -content-hash = "c7f6bec3d5de7faa9e801c4970989348601ff5dc104b58a89b6997a6031f9546" +content-hash = "6ef251dc05605bcb9c332680cf987cea03ced3f599266a8a23ba272eaf056b86" diff --git a/pyproject.toml b/pyproject.toml index 611ecc12a..df47df88a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,8 @@ flake8 = "7.2.0" pytest-benchmark = "5.1.0" pytest = "8.3.5" pytest-django = "4.11.1" +mypy = "1.16.1" +django-stubs = "5.2.1" ### # other stuff diff --git a/share/admin/celery.py b/share/admin/celery.py index 3537f78fc..e90a51ba4 100644 --- a/share/admin/celery.py +++ b/share/admin/celery.py @@ -39,8 +39,9 @@ def lookups(self, request, model_admin): return sorted((x, x.title()) for x in states.ALL_STATES) def queryset(self, request, queryset): - if self.value(): - return queryset.filter(status=self.value().upper()) + _value = self.value() + if _value: + return queryset.filter(status=_value.upper()) return queryset @@ -99,15 +100,15 @@ def status_(self, obj): self.STATUS_COLORS.get(obj.status, 'black'), obj.status.title() ) - status_.short_description = 'Status' + status_.short_description = 'Status' # type: ignore[attr-defined] def meta_(self, obj): return pprint.pformat(obj.meta) - meta_.short_description = 'Meta' + meta_.short_description = 'Meta' # type: ignore[attr-defined] def source_(self, obj): return obj.meta.get('source_config') or obj.meta.get('source') - source_.short_description = 'Source' + source_.short_description = 'Source' # type: ignore[attr-defined] def retry(self, request, queryset): for task in queryset: @@ -116,4 +117,4 @@ def retry(self, request, queryset): task.meta.get('kwargs', {}), task_id=str(task.task_id) ) - retry.short_description = 'Retry Tasks' + retry.short_description = 'Retry Tasks' # type: ignore[attr-defined] diff --git a/share/admin/util.py b/share/admin/util.py index fceb35677..2f75aa26a 100644 --- a/share/admin/util.py +++ b/share/admin/util.py @@ -1,6 +1,9 @@ -from django.contrib.admin import SimpleListFilter +from collections.abc import Callable, Sequence + +from django.contrib.admin import SimpleListFilter, ModelAdmin from django.core.paginator import Paginator from django.db import connection, transaction, OperationalError +from django.db.models import Model from django.utils.functional import cached_property from django.urls import reverse from django.utils.html import format_html @@ -46,7 +49,7 @@ def admin_link_html(linked_obj): return format_html('{}', url, repr(linked_obj)) -def linked_fk(field_name): +def linked_fk[T: type[ModelAdmin]](field_name: str) -> Callable[[T], T]: """Decorator that adds a link for a foreign key field """ def add_link(cls): @@ -54,7 +57,7 @@ def link(self, instance): linked_obj = getattr(instance, field_name) return admin_link_html(linked_obj) link_field = '{}_link'.format(field_name) - link.short_description = field_name.replace('_', ' ') + link.short_description = field_name.replace('_', ' ') # type: ignore[attr-defined] setattr(cls, link_field, link) append_to_cls_property(cls, 'readonly_fields', link_field) append_to_cls_property(cls, 'exclude', field_name) @@ -62,11 +65,15 @@ def link(self, instance): return add_link -def linked_many(field_name, order_by=None, select_related=None, defer=None): - """Decorator that adds links for a *-to-many field - """ - def add_links(cls): - def links(self, instance): +def linked_many[T: type[ModelAdmin]]( + field_name: str, + order_by: Sequence[str] = (), + select_related: Sequence[str] = (), + defer: Sequence[str] = (), +) -> Callable[[T], T]: + """Decorator that adds links for a *-to-many field""" + def add_links(cls: T) -> T: + def links(self, instance: Model) -> str: linked_qs = getattr(instance, field_name).all() if select_related: linked_qs = linked_qs.select_related(*select_related) @@ -81,8 +88,8 @@ def links(self, instance): for obj in linked_qs )) ) - links_field = '{}_links'.format(field_name) - links.short_description = field_name.replace('_', ' ') + links_field = f'{field_name}_links' + links.short_description = field_name.replace('_', ' ') # type: ignore[attr-defined] setattr(cls, links_field, links) append_to_cls_property(cls, 'readonly_fields', links_field) append_to_cls_property(cls, 'exclude', field_name) diff --git a/share/models/source_config.py b/share/models/source_config.py index a23dfcaf9..1f45d5c66 100644 --- a/share/models/source_config.py +++ b/share/models/source_config.py @@ -1,3 +1,4 @@ +from __future__ import annotations from django.db import models @@ -9,13 +10,13 @@ __all__ = ('SourceConfig',) -class SourceConfigManager(models.Manager): +class SourceConfigManager(models.Manager['SourceConfig']): use_in_migrations = True - def get_by_natural_key(self, key): + def get_by_natural_key(self, key) -> SourceConfig: return self.get(label=key) - def get_or_create_push_config(self, user, transformer_key=None): + def get_or_create_push_config(self, user, transformer_key=None) -> SourceConfig: assert isinstance(user, ShareUser) _config_label = '.'.join(( user.username, diff --git a/share/models/source_unique_identifier.py b/share/models/source_unique_identifier.py index 05c6eb7d5..63c8e3c85 100644 --- a/share/models/source_unique_identifier.py +++ b/share/models/source_unique_identifier.py @@ -19,7 +19,7 @@ class JSONAPIMeta(BaseJSONAPIMeta): class Meta: unique_together = ('identifier', 'source_config') - def get_backcompat_sharev2_suid(self): + def get_backcompat_sharev2_suid(self) -> 'SourceUniqueIdentifier': '''get an equivalent "v2_push" suid for this suid for filling the legacy suid-based sharev2 index with consistent doc ids diff --git a/share/oaipmh/util.py b/share/oaipmh/util.py index 3a033227a..413ac0173 100644 --- a/share/oaipmh/util.py +++ b/share/oaipmh/util.py @@ -1,3 +1,6 @@ +import datetime +from typing import Any + from lxml import etree from primitive_metadata import primitive_rdf @@ -5,7 +8,7 @@ from trove.vocab.namespaces import OAI, OAI_DC -def format_datetime(dt): +def format_datetime(dt: datetime.datetime | primitive_rdf.Literal | str) -> str: """OAI-PMH has specific time format requirements -- comply. """ if isinstance(dt, primitive_rdf.Literal): @@ -25,7 +28,7 @@ def format_datetime(dt): } -def ns(namespace_prefix, tag_name): +def ns(namespace_prefix: str, tag_name: str) -> str: """format XML tag/attribute name with full namespace URI see https://lxml.de/tutorial.html#namespaces @@ -33,7 +36,7 @@ def ns(namespace_prefix, tag_name): return f'{{{XML_NAMESPACES[namespace_prefix]}}}{tag_name}' -def nsmap(*namespace_prefixes, default=None): +def nsmap(*namespace_prefixes: str, default: str | None = None) -> dict[str | None, str]: """build a namespace map suitable for lxml see https://lxml.de/tutorial.html#namespaces @@ -49,7 +52,7 @@ def nsmap(*namespace_prefixes, default=None): # wrapper for lxml.etree.SubElement, adds `text` kwarg for convenience -def SubEl(parent, tag_name, text=None, **kwargs): +def SubEl(parent: etree.Element, tag_name: str, text: str | None = None, **kwargs: Any) -> etree.SubElement: element = etree.SubElement(parent, tag_name, **kwargs) if isinstance(text, primitive_rdf.Literal): _language_tag = text.language diff --git a/share/search/index_messenger.py b/share/search/index_messenger.py index 34cfb9e7d..f7f14f9e6 100644 --- a/share/search/index_messenger.py +++ b/share/search/index_messenger.py @@ -12,7 +12,7 @@ from share.search.messages import MessagesChunk, MessageType from share.search import index_strategy - +from trove.models import Indexcard logger = logging.getLogger(__name__) @@ -25,7 +25,7 @@ class IndexMessenger: 'max_retries': 30, # give up after 30 tries. } - def __init__(self, *, celery_app=None, index_strategys=None): + def __init__(self, *, celery_app=None, index_strategys=None) -> None: self.celery_app = ( celery.current_app if celery_app is None @@ -33,7 +33,7 @@ def __init__(self, *, celery_app=None, index_strategys=None): ) self.index_strategys = index_strategys or tuple(index_strategy.each_strategy()) - def notify_indexcard_update(self, indexcards, *, urgent=False): + def notify_indexcard_update(self, indexcards: list[Indexcard], *, urgent=False) -> None: self.send_messages_chunk( MessagesChunk( MessageType.UPDATE_INDEXCARD, @@ -53,7 +53,7 @@ def notify_indexcard_update(self, indexcards, *, urgent=False): urgent=urgent, ) - def notify_suid_update(self, suid_ids, *, urgent=False): + def notify_suid_update(self, suid_ids, *, urgent=False) -> None: self.send_messages_chunk( MessagesChunk(MessageType.INDEX_SUID, suid_ids), urgent=urgent, diff --git a/share/util/__init__.py b/share/util/__init__.py index bd9aa831a..6b174eeda 100644 --- a/share/util/__init__.py +++ b/share/util/__init__.py @@ -24,7 +24,7 @@ class IDObfuscator: ID_RE = re.compile(r'([0-9A-Fa-f]{2,})([0-9A-Fa-f]{3})-([0-9A-Fa-f]{3})-([0-9A-Fa-f]{3})') @classmethod - def encode(cls, instance): + def encode(cls, instance) -> str: return cls.encode_id(instance.id, instance._meta.model) @classmethod diff --git a/share/util/checksum_iri.py b/share/util/checksum_iri.py index 552aeb91c..b0dafa908 100644 --- a/share/util/checksum_iri.py +++ b/share/util/checksum_iri.py @@ -1,9 +1,18 @@ +from __future__ import annotations +from collections.abc import Callable import dataclasses import hashlib import json +from typing import Self, Any, TYPE_CHECKING +if TYPE_CHECKING: + from trove.util.json import JsonValue -def _ensure_bytes(bytes_or_something) -> bytes: + +type HexdigestFn = Callable[[str | bytes, str | bytes], str] + + +def _ensure_bytes(bytes_or_something: bytes | str) -> bytes: if isinstance(bytes_or_something, bytes): return bytes_or_something if isinstance(bytes_or_something, str): @@ -11,12 +20,12 @@ def _ensure_bytes(bytes_or_something) -> bytes: raise NotImplementedError(f'how bytes? ({bytes_or_something})') -def _builtin_checksum(hash_constructor): +def _builtin_checksum(hash_constructor: Any) -> HexdigestFn: def hexdigest_fn(salt: str | bytes, data: str | bytes) -> str: hasher = hash_constructor() hasher.update(_ensure_bytes(salt)) hasher.update(_ensure_bytes(data)) - return hasher.hexdigest() + return str(hasher.hexdigest()) return hexdigest_fn @@ -33,11 +42,11 @@ class ChecksumIri: salt: str hexdigest: str - def __str__(self): + def __str__(self) -> str: return f'urn:checksum:{self.checksumalgorithm_name}:{self.salt}:{self.hexdigest}' @classmethod - def digest(cls, checksumalgorithm_name: str, *, salt: str, data: str): + def digest(cls, checksumalgorithm_name: str, *, salt: str, data: str) -> Self: try: hexdigest_fn = CHECKSUM_ALGORITHMS[checksumalgorithm_name] except KeyError: @@ -52,7 +61,7 @@ def digest(cls, checksumalgorithm_name: str, *, salt: str, data: str): ) @classmethod - def digest_json(cls, checksumalgorithm_name, *, salt, raw_json): + def digest_json(cls, checksumalgorithm_name: str, *, salt: str, raw_json: JsonValue) -> Self: return cls.digest( checksumalgorithm_name, salt=salt, @@ -60,7 +69,7 @@ def digest_json(cls, checksumalgorithm_name, *, salt, raw_json): ) @classmethod - def from_iri(cls, checksum_iri: str): + def from_iri(cls, checksum_iri: str) -> Self: try: (urn, checksum, algorithmname, salt, hexdigest) = checksum_iri.split(':') assert (urn, checksum) == ('urn', 'checksum') diff --git a/tests/trove/render/test_jsonld_renderer.py b/tests/trove/render/test_jsonld_renderer.py index 75b92f9ff..eef657f1d 100644 --- a/tests/trove/render/test_jsonld_renderer.py +++ b/tests/trove/render/test_jsonld_renderer.py @@ -26,12 +26,10 @@ class TestJsonldRenderer(_base.TroveJsonRendererTests): "@value": "2024-01-01" } ], - "foaf:primaryTopic": [ - "blarg:anItem" - ], + "foaf:primaryTopic": [{"@id": "blarg:anItem"}], "rdf:type": [ - "trove:Indexcard", - "dcat:CatalogRecord" + {"@id": "trove:Indexcard"}, + {"@id": "dcat:CatalogRecord"} ], "trove:focusIdentifier": [ { @@ -61,7 +59,7 @@ class TestJsonldRenderer(_base.TroveJsonRendererTests): } ], "blarg:hasIri": [ - "blarg:anIri" + {"@id": "blarg:anIri"} ], "blarg:hasRdfLangStringLiteral": [ { @@ -80,7 +78,7 @@ class TestJsonldRenderer(_base.TroveJsonRendererTests): "@value": "a literal of strange datatype" } ], - "rdf:type": ["blarg:aType"], + "rdf:type": [{"@id": "blarg:aType"}], }), ), } @@ -95,7 +93,7 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): rendered_content=json.dumps({ "@id": "blarg:aSearch", "rdf:type": [ - "trove:Cardsearch" + {"@id": "trove:Cardsearch"} ], "trove:totalResultCount": { "@type": "xsd:integer", @@ -108,7 +106,7 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): rendered_content=json.dumps({ "@id": "blarg:aSearchFew", "rdf:type": [ - "trove:Cardsearch" + {"@id": "trove:Cardsearch"} ], "trove:totalResultCount": { "@type": "xsd:integer", @@ -119,7 +117,7 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): "@list": [ { "rdf:type": [ - "trove:SearchResult" + {"@id": "trove:SearchResult"} ], "trove:indexCard": { "@id": "blarg:aCard", @@ -136,16 +134,14 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): } ], "foaf:primaryTopic": [ - "blarg:anItem" + {"@id": "blarg:anItem"} ], "rdf:type": [ - "trove:Indexcard", - "dcat:CatalogRecord" + {"@id": "trove:Indexcard"}, + {"@id": "dcat:CatalogRecord"} ], "trove:focusIdentifier": [ - { - "@value": BLARG.anItem - } + {"@value": BLARG.anItem} ], "trove:resourceMetadata": { "@id": BLARG.anItem, @@ -155,7 +151,7 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): }, { "rdf:type": [ - "trove:SearchResult" + {"@id": "trove:SearchResult"} ], "trove:indexCard": { "@id": "blarg:aCardd", @@ -172,11 +168,11 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): } ], "foaf:primaryTopic": [ - "blarg:anItemm" + {"@id": "blarg:anItemm"} ], "rdf:type": [ - "trove:Indexcard", - "dcat:CatalogRecord" + {"@id": "trove:Indexcard"}, + {"@id": "dcat:CatalogRecord"} ], "trove:focusIdentifier": [ { @@ -191,7 +187,7 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): }, { "rdf:type": [ - "trove:SearchResult" + {"@id": "trove:SearchResult"} ], "trove:indexCard": { "@id": "blarg:aCarddd", @@ -208,16 +204,14 @@ class TestJsonldSearchRenderer(_base.TrovesearchJsonRendererTests): } ], "foaf:primaryTopic": [ - "blarg:anItemmm" + {"@id": "blarg:anItemmm"} ], "rdf:type": [ - "trove:Indexcard", - "dcat:CatalogRecord" + {"@id": "trove:Indexcard"}, + {"@id": "dcat:CatalogRecord"} ], "trove:focusIdentifier": [ - { - "@value": BLARG.anItemmm - } + {"@value": BLARG.anItemmm} ], "trove:resourceMetadata": { "@id": BLARG.anItemmm, diff --git a/trove/admin.py b/trove/admin.py index 5ef20eac3..4df52b10c 100644 --- a/trove/admin.py +++ b/trove/admin.py @@ -1,3 +1,5 @@ +from __future__ import annotations +from typing import Any from django.contrib import admin from django.utils.html import format_html @@ -52,9 +54,9 @@ class IndexcardAdmin(admin.ModelAdmin): list_filter = ('deleted', 'source_record_suid__source_config') actions = ('_freshen_index',) - def _freshen_index(self, request, queryset): + def _freshen_index(self, queryset: list[Indexcard]) -> None: IndexMessenger().notify_indexcard_update(queryset) - _freshen_index.short_description = 'freshen indexcard in search index' + _freshen_index.short_description = 'freshen indexcard in search index' # type: ignore[attr-defined] @admin.register(LatestResourceDescription, site=admin_site) @@ -73,9 +75,9 @@ class LatestResourceDescriptionAdmin(admin.ModelAdmin): list_select_related = ('indexcard',) show_full_result_count = False - def rdf_as_turtle__pre(self, instance): + def rdf_as_turtle__pre(self, instance: Any) -> str: return format_html('
{}', instance.rdf_as_turtle)
- rdf_as_turtle__pre.short_description = 'rdf as turtle'
+ rdf_as_turtle__pre.short_description = 'rdf as turtle' # type: ignore[attr-defined]
@admin.register(ArchivedResourceDescription, site=admin_site)
@@ -94,9 +96,9 @@ class ArchivedResourceDescriptionAdmin(admin.ModelAdmin):
list_select_related = ('indexcard',)
show_full_result_count = False
- def rdf_as_turtle__pre(self, instance):
+ def rdf_as_turtle__pre(self, instance: Any) -> str:
return format_html('{}', instance.rdf_as_turtle)
- rdf_as_turtle__pre.short_description = 'rdf as turtle'
+ rdf_as_turtle__pre.short_description = 'rdf as turtle' # type: ignore[attr-defined]
@admin.register(SupplementaryResourceDescription, site=admin_site)
@@ -116,9 +118,9 @@ class SupplementaryResourceDescriptionAdmin(admin.ModelAdmin):
list_select_related = ('indexcard',)
show_full_result_count = False
- def rdf_as_turtle__pre(self, instance):
+ def rdf_as_turtle__pre(self, instance: SupplementaryResourceDescription) -> str:
return format_html('{}', instance.rdf_as_turtle)
- rdf_as_turtle__pre.short_description = 'rdf as turtle'
+ rdf_as_turtle__pre.short_description = 'rdf as turtle' # type: ignore[attr-defined]
@admin.register(DerivedIndexcard, site=admin_site)
diff --git a/trove/derive/__init__.py b/trove/derive/__init__.py
index 3cdd089f7..1f7d24c13 100644
--- a/trove/derive/__init__.py
+++ b/trove/derive/__init__.py
@@ -1,8 +1,14 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+
from . import (
sharev2_elastic,
osfmap_json_mini,
oaidc_xml, osfmap_json,
)
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+ from ._base import IndexcardDeriver
DERIVER_SET = (
sharev2_elastic.ShareV2ElasticDeriver,
@@ -15,18 +21,20 @@
# property_label?
)
-DEFAULT_DERIVER_SET = (
+DEFAULT_DERIVER_SET: tuple[type[IndexcardDeriver], ...] = (
sharev2_elastic.ShareV2ElasticDeriver,
osfmap_json_mini.OsfmapJsonMiniDeriver,
oaidc_xml.OaiDcXmlDeriver,
)
-def get_deriver_classes(deriver_iri_filter=None):
+def get_deriver_classes(
+ deriver_iri_filter: Iterable[str] | None = None,
+) -> tuple[type[IndexcardDeriver], ...]:
if deriver_iri_filter is None:
return DEFAULT_DERIVER_SET
- return [
+ return tuple(
_deriver_class
for _deriver_class in DERIVER_SET
if _deriver_class.deriver_iri() in deriver_iri_filter
- ]
+ )
diff --git a/trove/derive/_base.py b/trove/derive/_base.py
index bc8d8b583..ea2a11c16 100644
--- a/trove/derive/_base.py
+++ b/trove/derive/_base.py
@@ -1,5 +1,5 @@
import abc
-
+from typing import Any
from primitive_metadata import primitive_rdf
from trove.models.resource_description import ResourceDescription
@@ -15,7 +15,7 @@ def __init__(self, upstream_description: ResourceDescription):
self.focus_iri = upstream_description.focus_iri
self.data = upstream_description.as_rdfdoc_with_supplements()
- def q(self, pathset):
+ def q(self, pathset: Any) -> Any:
# convenience for querying self.data on self.focus_iri
return self.data.q(self.focus_iri, pathset)
diff --git a/trove/derive/oaidc_xml.py b/trove/derive/oaidc_xml.py
index f22caa4dc..610fb49fc 100644
--- a/trove/derive/oaidc_xml.py
+++ b/trove/derive/oaidc_xml.py
@@ -1,3 +1,4 @@
+from typing import Any
from lxml import etree
from primitive_metadata import primitive_rdf as rdf
@@ -63,7 +64,7 @@ def should_skip(self) -> bool:
return _allowed_focustype_iris.isdisjoint(_focustype_iris)
# abstract method from IndexcardDeriver
- def derive_card_as_text(self):
+ def derive_card_as_text(self) -> Any:
_dc_element = self._derive_card_as_xml()
return etree.tostring(_dc_element, encoding='unicode')
diff --git a/trove/derive/osfmap_json.py b/trove/derive/osfmap_json.py
index 4e8147483..69de39b26 100644
--- a/trove/derive/osfmap_json.py
+++ b/trove/derive/osfmap_json.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
import datetime
import json
+from typing import TYPE_CHECKING
from primitive_metadata import primitive_rdf as rdf
@@ -10,13 +12,17 @@
osfmap_json_shorthand,
)
from ._base import IndexcardDeriver
+if TYPE_CHECKING:
+ from trove.util.json import JsonValue, JsonObject
class OsfmapJsonFullDeriver(IndexcardDeriver):
# abstract method from IndexcardDeriver
@staticmethod
def deriver_iri() -> str:
- return TROVE['derive/osfmap_json_full']
+ _iri = TROVE['derive/osfmap_json_full']
+ assert isinstance(_iri, str)
+ return _iri
# abstract method from IndexcardDeriver
@staticmethod
@@ -28,7 +34,7 @@ def should_skip(self) -> bool:
return False
# abstract method from IndexcardDeriver
- def derive_card_as_text(self):
+ def derive_card_as_text(self) -> str:
return json.dumps(
_RdfOsfmapJsonldRenderer().tripledict_as_nested_jsonld(
self.data.tripledict,
@@ -38,13 +44,13 @@ def derive_card_as_text(self):
class _RdfOsfmapJsonldRenderer:
- __nestvisiting_iris: set
+ __nestvisiting_iris: set[str]
- def tripledict_as_nested_jsonld(self, tripledict: rdf.RdfTripleDictionary, focus_iri: str):
+ def tripledict_as_nested_jsonld(self, tripledict: rdf.RdfTripleDictionary, focus_iri: str) -> JsonObject:
self.__nestvisiting_iris = set()
return self.__nested_rdfobject_as_jsonld(tripledict, focus_iri)
- def rdfobject_as_jsonld(self, rdfobject: rdf.RdfObject) -> dict:
+ def rdfobject_as_jsonld(self, rdfobject: rdf.RdfObject) -> JsonObject:
if isinstance(rdfobject, frozenset):
return self.twopledict_as_jsonld(
rdf.twopledict_from_twopleset(rdfobject),
@@ -87,7 +93,7 @@ def rdfobject_as_jsonld(self, rdfobject: rdf.RdfObject) -> dict:
]}
raise trove_exceptions.UnsupportedRdfObject(rdfobject)
- def twopledict_as_jsonld(self, twopledict: rdf.RdfTwopleDictionary) -> dict:
+ def twopledict_as_jsonld(self, twopledict: rdf.RdfTwopleDictionary) -> JsonObject:
_jsonld = {}
for _pred, _objectset in twopledict.items():
if _objectset:
@@ -102,7 +108,7 @@ def __nested_rdfobject_as_jsonld(
self,
tripledict: rdf.RdfTripleDictionary,
rdfobject: rdf.RdfObject,
- ):
+ ) -> JsonObject:
_yes_nest = (
isinstance(rdfobject, str)
and (rdfobject not in self.__nestvisiting_iris)
@@ -129,7 +135,7 @@ def __nested_rdfobject_as_jsonld(
self.__nestvisiting_iris.discard(rdfobject)
return _nested_obj
- def _list_or_single_value(self, predicate_iri, json_list: list):
+ def _list_or_single_value(self, predicate_iri: str, json_list: list[JsonValue]) -> JsonValue:
_only_one_object = OWL.FunctionalProperty in (
OSFMAP_THESAURUS
.get(predicate_iri, {})
diff --git a/trove/derive/osfmap_json_mini.py b/trove/derive/osfmap_json_mini.py
index cd4520f62..ad043e419 100644
--- a/trove/derive/osfmap_json_mini.py
+++ b/trove/derive/osfmap_json_mini.py
@@ -1,6 +1,11 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+
from trove.vocab import namespaces as ns
from trove.derive.osfmap_json import OsfmapJsonFullDeriver
from trove.vocab.namespaces import TROVE
+if TYPE_CHECKING:
+ from trove.models.resource_description import ResourceDescription
EXCLUDED_PREDICATE_SET = frozenset({
ns.OSFMAP.contains,
@@ -8,15 +13,15 @@
class OsfmapJsonMiniDeriver(OsfmapJsonFullDeriver):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
+ def __init__(self, upstream_description: ResourceDescription):
+ super().__init__(upstream_description)
self.convert_tripledict()
@staticmethod
def deriver_iri() -> str:
return TROVE['derive/osfmap_json']
- def convert_tripledict(self):
+ def convert_tripledict(self) -> None:
self.data.tripledict = {
_subj: _new_twopledict
for _subj, _old_twopledict in self.data.tripledict.items()
diff --git a/trove/derive/sharev2_elastic.py b/trove/derive/sharev2_elastic.py
index 27c7b3a06..ab65d757e 100644
--- a/trove/derive/sharev2_elastic.py
+++ b/trove/derive/sharev2_elastic.py
@@ -1,6 +1,7 @@
import datetime
import json
import re
+from typing import Union, Dict, Any, List, Tuple, Optional, Set
from primitive_metadata import primitive_rdf
@@ -98,7 +99,10 @@
EMPTY_VALUES = (None, '', []) # type: ignore[var-annotated]
-def strip_empty_values(thing):
+JSONLike = Union[Dict[str, Any], List[Any], Tuple[Any, ...], Any]
+
+
+def strip_empty_values(thing: JSONLike) -> JSONLike:
if isinstance(thing, dict):
return {
k: strip_empty_values(v)
@@ -145,7 +149,7 @@ def should_skip(self) -> bool:
return _allowed_focustype_iris.isdisjoint(_focustype_iris)
# abstract method from IndexcardDeriver
- def derive_card_as_text(self):
+ def derive_card_as_text(self) -> str:
_suid = self.upstream_description.indexcard.source_record_suid
try: # maintain doc id in the sharev2 index
_suid = _suid.get_backcompat_sharev2_suid()
@@ -204,7 +208,7 @@ def derive_card_as_text(self):
sort_keys=True,
)
- def _related_names(self, *predicate_iris):
+ def _related_names(self, *predicate_iris: Tuple[Dict[str, Any]]) -> List[None | str | Any]:
_obj_iter = self.data.q(
self.focus_iri,
{
@@ -217,7 +221,7 @@ def _related_names(self, *predicate_iris):
for _obj in _obj_iter
]
- def _single_date(self, *predicate_iris, focus_iri=None):
+ def _single_date(self, *predicate_iris: Tuple[Any], focus_iri: Optional[str] = None) -> None | str | Any:
_val = self._single_value(*predicate_iris, focus_iri=focus_iri)
if isinstance(_val, primitive_rdf.Literal):
return _val.unicode_value
@@ -225,10 +229,10 @@ def _single_date(self, *predicate_iris, focus_iri=None):
return _val.isoformat()
return _val
- def _single_string(self, *predicate_iris, focus_iri=None):
+ def _single_string(self, *predicate_iris: Tuple[Any], focus_iri: Optional[str] = None) -> None | str | Any:
return _obj_to_string_or_none(self._single_value(*predicate_iris, focus_iri=focus_iri))
- def _single_value(self, *predicate_iris, focus_iri=None):
+ def _single_value(self, *predicate_iris: Tuple[Any], focus_iri: Optional[str] = None) -> None | str | Any:
# for sharev2 back-compat, some fields must have a single value
# (tho now the corresponding rdf property may have many values)
for _pred in predicate_iris:
@@ -242,7 +246,7 @@ def _single_value(self, *predicate_iris, focus_iri=None):
continue
return None
- def _string_list(self, *predicate_paths, focus_iri=None):
+ def _string_list(self, *predicate_paths: Tuple[Any], focus_iri: Optional[str] = None) -> List[Any]:
_object_iter = self.data.q(
focus_iri or self.focus_iri,
predicate_paths,
@@ -266,7 +270,7 @@ def _osf_related_resource_types(self) -> dict[str, bool]:
for _key, _pred in _osf_artifact_types.items()
}
- def _related_agent_list(self, *predicate_iris, focus_iri=None):
+ def _related_agent_list(self, *predicate_iris: str, focus_iri: Optional[str] = None) -> List[Dict[str, Any]]:
_agent_list = []
for _predicate_iri in predicate_iris:
_agent_iri_iter = self.data.q(
@@ -277,7 +281,7 @@ def _related_agent_list(self, *predicate_iris, focus_iri=None):
_agent_list.append(self._related_agent(_predicate_iri, _agent_iri))
return _agent_list
- def _related_agent(self, relation_iri, agent_iri):
+ def _related_agent(self, relation_iri: str, agent_iri: str) -> Dict[str, Any]:
return {
'type': self._single_type(agent_iri),
'types': self._type_list(agent_iri),
@@ -288,7 +292,7 @@ def _related_agent(self, relation_iri, agent_iri):
# TODO 'order_cited':
}
- def _single_type_iri(self, type_iris) -> str | None:
+ def _single_type_iri(self, type_iris: List[Any] | Set[Any]) -> str | None | Any:
# try SHAREv2 types
_sharev2_type_iris = set(filter(SHAREv2.__contains__, type_iris))
if _sharev2_type_iris:
@@ -309,7 +313,7 @@ def _single_type_iri(self, type_iris) -> str | None:
return self._single_type_iri([SHAREv2[_typename], SHAREv2.CreativeWork])
return None
- def _single_type(self, focus_iri):
+ def _single_type(self, focus_iri: str) -> str | None:
_type_iris = set(self.data.q(focus_iri, RDF.type))
_type_iri = self._single_type_iri(_type_iris)
return (
@@ -318,27 +322,27 @@ def _single_type(self, focus_iri):
else None
)
- def _type_list(self, focus_iri):
+ def _type_list(self, focus_iri: str) -> list[str]:
return sorted(
self._format_type_iri(_type_iri)
for _type_iri in self.data.q(focus_iri, RDF.type)
if _type_iri in SHAREv2 or _type_iri in OSFMAP
)
- def _format_type_iri(self, iri):
+ def _format_type_iri(self, iri: str) -> str:
if iri in SHAREv2:
_typename = primitive_rdf.iri_minus_namespace(iri, namespace=SHAREv2)
elif iri in OSFMAP:
_typename = primitive_rdf.iri_minus_namespace(iri, namespace=OSFMAP)
else:
- return iri # oh well
+ return iri
return self._format_typename(_typename)
- def _format_typename(self, sharev2_typename: str):
+ def _format_typename(self, sharev2_typename: str) -> str:
# convert from PascalCase to lower case with spaces between words
return re.sub(r'\B([A-Z])', r' \1', sharev2_typename).lower()
- def _work_lineage_list(self, work_iri):
+ def _work_lineage_list(self, work_iri: str) -> List[Optional[Dict[str, Any]]]:
# expects a linear lineage (each resource only "part of" one other)
_parent_iri = self._single_value(DCTERMS.isPartOf, focus_iri=work_iri)
if isinstance(_parent_iri, str):
@@ -350,7 +354,7 @@ def _work_lineage_list(self, work_iri):
else:
return []
- def _work_lineage_item(self, work_iri):
+ def _work_lineage_item(self, work_iri: str) -> Dict[str, Any]:
return {
'type': self._single_type(work_iri),
'types': self._type_list(work_iri),
@@ -358,7 +362,7 @@ def _work_lineage_item(self, work_iri):
'identifiers': self._string_list(DCTERMS.identifier, focus_iri=work_iri),
}
- def _subjects_and_synonyms(self, source_name):
+ def _subjects_and_synonyms(self, source_name: str) -> Tuple[List[str], List[str]]:
_subjects = []
_subject_synonyms = []
# making extra osf-specific assumptions here
@@ -374,7 +378,12 @@ def _subjects_and_synonyms(self, source_name):
_subjects.append(_serialize_subject('bepress', _bepress_lineage))
return _subjects, _subject_synonyms
- def _subject_lineage(self, subject_iri, label_predicate_iri, visiting_set=None) -> tuple[str, ...]:
+ def _subject_lineage(
+ self,
+ subject_iri: str,
+ label_predicate_iri: str,
+ visiting_set: Optional[Set[str]] = None
+ ) -> Tuple[str, ...]:
_visiting_set = visiting_set or set()
_visiting_set.add(subject_iri)
_labeltext = next(self.data.q(subject_iri, label_predicate_iri), None)
@@ -391,7 +400,7 @@ def _serialize_subject(taxonomy_name: str, subject_lineage: tuple[str, ...]) ->
return '|'.join((taxonomy_name, *subject_lineage))
-def _obj_to_string_or_none(obj):
+def _obj_to_string_or_none(obj: Optional[str]) -> None | str | Any:
if obj is None:
return None
if isinstance(obj, primitive_rdf.Literal):
diff --git a/trove/digestive_tract.py b/trove/digestive_tract.py
index a91a9d633..d6cab1ea9 100644
--- a/trove/digestive_tract.py
+++ b/trove/digestive_tract.py
@@ -11,9 +11,11 @@
import copy
import datetime
import logging
+from typing import Iterable
import celery
from django.db import transaction
+from django.db.models import QuerySet
from primitive_metadata import primitive_rdf
from share import models as share_db
@@ -39,12 +41,12 @@ def ingest(
focus_iri: str,
record_mediatype: str,
raw_record: str,
- record_identifier: str = '', # default focus_iri
+ record_identifier: str | None = None, # default focus_iri
is_supplementary: bool = False,
expiration_date: datetime.date | None = None, # default "never"
restore_deleted: bool = False,
urgent: bool = False,
-):
+) -> None:
'''ingest: shorthand for sniff + extract + (eventual) derive'''
_suid = sniff(
from_user=from_user,
@@ -71,7 +73,7 @@ def sniff(
*, # all keyword-args
from_user: share_db.ShareUser,
focus_iri: str,
- record_identifier: str = '',
+ record_identifier: str | None = None,
is_supplementary: bool = False,
) -> share_db.SourceUniqueIdentifier:
'''sniff: get a vague sense of a metadata record without touching the record itself
@@ -134,7 +136,7 @@ def extract(
if (expiration_date is not None) and (expiration_date <= datetime.date.today()):
raise CannotDigestExpiredDatum(suid, expiration_date)
_tripledicts_by_focus_iri = {}
- _extractor = get_rdf_extractor_class(record_mediatype)(suid.source_config)
+ _extractor = get_rdf_extractor_class(record_mediatype)()
# TODO normalize (or just validate) tripledict:
# - synonymous iris should be grouped (only one as subject-key, others under owl:sameAs)
# - focus should have rdf:type
@@ -142,6 +144,7 @@ def extract(
# - connected graph (all subject-key iris reachable from focus, or reverse for vocab terms?)
_extracted_tripledict: primitive_rdf.RdfTripleDictionary = _extractor.extract_rdf(raw_record)
if _extracted_tripledict:
+ assert suid.focus_identifier is not None
try:
_focus_iri = suid.focus_identifier.find_equivalent_iri(_extracted_tripledict)
except ValueError:
@@ -173,7 +176,7 @@ def extract(
)
-def derive(indexcard: trove_db.Indexcard, deriver_iris=None):
+def derive(indexcard: trove_db.Indexcard, deriver_iris: Iterable[str] | None = None) -> list[trove_db.DerivedIndexcard]:
'''derive: build other kinds of index cards from the extracted rdf
will create, update, or delete:
@@ -209,7 +212,7 @@ def derive(indexcard: trove_db.Indexcard, deriver_iris=None):
return _derived_list
-def expel(from_user: share_db.ShareUser, record_identifier: str):
+def expel(from_user: share_db.ShareUser, record_identifier: str) -> None:
_suid_qs = share_db.SourceUniqueIdentifier.objects.filter(
source_config__source__user=from_user,
identifier=record_identifier,
@@ -238,7 +241,7 @@ def expel_expired_data(today: datetime.date) -> None:
)
-def _expel_supplementary_descriptions(supplementary_rdf_queryset) -> None:
+def _expel_supplementary_descriptions(supplementary_rdf_queryset: QuerySet[trove_db.SupplementaryResourceDescription]) -> None:
# delete expired supplementary metadata
_affected_indexcards = set()
for _supplement in supplementary_rdf_queryset.select_related('indexcard'):
@@ -256,9 +259,9 @@ def task__derive(
task: celery.Task,
indexcard_id: int,
deriver_iri: str | None = None,
- notify_index=True,
- urgent=False,
-):
+ notify_index: bool = True,
+ urgent: bool = False,
+) -> None:
_indexcard = trove_db.Indexcard.objects.get(id=indexcard_id)
derive(
_indexcard,
@@ -271,7 +274,7 @@ def task__derive(
@celery.shared_task(acks_late=True)
-def task__schedule_derive_for_source_config(source_config_id: int, notify_index=False):
+def task__schedule_derive_for_source_config(source_config_id: int, notify_index: bool = False) -> None:
_indexcard_id_qs = (
trove_db.Indexcard.objects
.filter(source_record_suid__source_config_id=source_config_id)
@@ -282,7 +285,7 @@ def task__schedule_derive_for_source_config(source_config_id: int, notify_index=
@celery.shared_task(acks_late=True)
-def task__schedule_all_for_deriver(deriver_iri: str, notify_index=False):
+def task__schedule_all_for_deriver(deriver_iri: str, notify_index: bool = False) -> None:
if not get_deriver_classes([deriver_iri]):
raise DigestiveError(f'unknown deriver_iri: {deriver_iri}')
_indexcard_id_qs = (
@@ -294,5 +297,5 @@ def task__schedule_all_for_deriver(deriver_iri: str, notify_index=False):
@celery.shared_task(acks_late=True)
-def task__expel_expired_data():
+def task__expel_expired_data() -> None:
expel_expired_data(datetime.date.today())
diff --git a/trove/exceptions.py b/trove/exceptions.py
index 37cd4bfd7..1e5a5ab73 100644
--- a/trove/exceptions.py
+++ b/trove/exceptions.py
@@ -7,7 +7,7 @@ class TroveError(Exception):
http_status: int = http.HTTPStatus.INTERNAL_SERVER_ERROR
error_location: str = ''
- def __init__(self, *args):
+ def __init__(self, *args: object) -> None:
super().__init__(*args)
self.error_location = _get_nearest_code_location()
diff --git a/trove/extract/__init__.py b/trove/extract/__init__.py
index b2bde949e..3da9599a1 100644
--- a/trove/extract/__init__.py
+++ b/trove/extract/__init__.py
@@ -7,7 +7,7 @@
__all__ = ('get_rdf_extractor_class',)
-def get_rdf_extractor_class(mediatype) -> type[BaseRdfExtractor]:
+def get_rdf_extractor_class(mediatype: str) -> type[BaseRdfExtractor]:
if mediatype == 'text/turtle':
return TurtleRdfExtractor
raise trove_exceptions.CannotDigestMediatype(mediatype)
diff --git a/trove/extract/_base.py b/trove/extract/_base.py
index 618e688f7..58344a8f4 100644
--- a/trove/extract/_base.py
+++ b/trove/extract/_base.py
@@ -4,9 +4,6 @@
class BaseRdfExtractor(abc.ABC):
- def __init__(self, source_config):
- self.source_config = source_config
-
@abc.abstractmethod
def extract_rdf(self, input_document: str) -> primitive_rdf.RdfTripleDictionary:
raise NotImplementedError
diff --git a/trove/extract/turtle.py b/trove/extract/turtle.py
index 79018f70f..fb3666a94 100644
--- a/trove/extract/turtle.py
+++ b/trove/extract/turtle.py
@@ -4,5 +4,5 @@
class TurtleRdfExtractor(BaseRdfExtractor):
- def extract_rdf(self, input_document):
+ def extract_rdf(self, input_document: str): # type: ignore
return primitive_rdf.tripledict_from_turtle(input_document)
diff --git a/trove/management/commands/migrate_rawdatum_expiration.py b/trove/management/commands/migrate_rawdatum_expiration.py
deleted file mode 100644
index b0373b35f..000000000
--- a/trove/management/commands/migrate_rawdatum_expiration.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import datetime
-import time
-
-from django.db.models import OuterRef
-
-from trove.util.django import pk_chunked
-
-from share import models as share_db
-from share.management.commands import BaseShareCommand
-from trove import models as trove_db
-
-
-class Command(BaseShareCommand):
- # copy all non-null values from `RawDatum.expiration_date` to `SupplementaryIndexcardRdf.expiration_date`
- # (while being overly cautious to avoid joins on `RawDatum` or `SourceUniqueIdentifier`)
- # meant to be run after trove migration 0008_expiration_dates, before share.RawDatum is deleted
-
- def add_arguments(self, parser):
- parser.add_argument('--chunk-size', type=int, default=666)
- parser.add_argument('--today', type=datetime.date.fromisoformat, default=datetime.date.today())
- parser.add_argument('--continue-after', type=str, default=None)
-
- def handle(self, *args, chunk_size: int, today: datetime.date, continue_after, **kwargs):
- _before = time.perf_counter()
- _total_updated = 0
- _raw_qs = (
- share_db.RawDatum.objects.latest_for_each_suid()
- .filter(expiration_date__gt=today) # ignore the expired (and the non-expiring)
- )
- if continue_after is not None:
- _raw_qs = _raw_qs.filter(pk__gt=continue_after)
- for _raw_pk_chunk in pk_chunked(_raw_qs, chunk_size):
- _supp_qs = trove_db.SupplementaryIndexcardRdf.objects.filter(
- from_raw_datum_id__in=_raw_pk_chunk,
- expiration_date__isnull=True, # avoid overwriting non-null values
- )
- _updated_count = _supp_qs.update(
- expiration_date=share_db.RawDatum.objects.filter(
- id=OuterRef('from_raw_datum_id'),
- ).values('expiration_date'),
- )
- _total_updated += _updated_count
- _last_pk = _raw_pk_chunk[-1]
- _elapsed = time.perf_counter() - _before
- self.stdout.write(
- f'{_elapsed:.2f}: migrated {_updated_count} of {len(_raw_pk_chunk)} --continue-after={_last_pk}',
- )
- _total_seconds = time.perf_counter() - _before
- self.stdout.write(
- self.style.SUCCESS(f'done! migrated {_total_updated} in {_total_seconds}s'),
- )
diff --git a/trove/models/derived_indexcard.py b/trove/models/derived_indexcard.py
index 52f0d3989..8b7fe55a5 100644
--- a/trove/models/derived_indexcard.py
+++ b/trove/models/derived_indexcard.py
@@ -1,9 +1,12 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
from django.db import models
from primitive_metadata import primitive_rdf as rdf
from trove.models.resource_identifier import ResourceIdentifier
+if TYPE_CHECKING:
+ from trove.derive._base import IndexcardDeriver
__all__ = ('DerivedIndexcard',)
@@ -31,14 +34,14 @@ class Meta:
),
]
- def __repr__(self):
+ def __repr__(self) -> str:
return f'<{self.__class__.__qualname__}({self.id}, {self.upriver_indexcard.uuid}, "{self.deriver_identifier.sufficiently_unique_iri}")'
- def __str__(self):
+ def __str__(self) -> str:
return repr(self)
@property
- def deriver_cls(self):
+ def deriver_cls(self) -> type[IndexcardDeriver]:
from trove.derive import get_deriver_classes
(_deriver_cls,) = get_deriver_classes(self.deriver_identifier.raw_iri_list)
return _deriver_cls
diff --git a/trove/models/indexcard.py b/trove/models/indexcard.py
index ba6de67d3..751011f57 100644
--- a/trove/models/indexcard.py
+++ b/trove/models/indexcard.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import datetime
import uuid
+from typing import Any
from django.db import models
from django.db import transaction
@@ -25,8 +26,8 @@
__all__ = ('Indexcard',)
-class IndexcardManager(models.Manager):
- def get_for_iri(self, iri: str):
+class IndexcardManager(models.Manager['Indexcard']):
+ def get_for_iri(self, iri: str) -> Indexcard:
_uuid = rdf.iri_minus_namespace(iri, namespace=trove_indexcard_namespace())
return self.get(uuid=_uuid)
@@ -49,7 +50,7 @@ def save_indexcards_from_tripledicts(
restore_deleted=restore_deleted,
expiration_date=expiration_date,
)
- _focus_identifier_ids = {_fid.id for _fid in _indexcard.focus_identifier_set.all()}
+ _focus_identifier_ids = {str(_fid.id) for _fid in _indexcard.focus_identifier_set.all()}
if not _seen_focus_identifier_ids.isdisjoint(_focus_identifier_ids):
_duplicates = (
ResourceIdentifier.objects
@@ -104,7 +105,7 @@ def save_indexcard_from_tripledict(
focus_iri: str,
restore_deleted: bool = False,
expiration_date: datetime.date | None = None,
- ):
+ ) -> Indexcard:
assert not suid.is_supplementary
_focus_identifier_set = (
ResourceIdentifier.objects
@@ -114,7 +115,7 @@ def save_indexcard_from_tripledict(
ResourceIdentifier.objects.get_or_create_for_iri(_iri)
for _iri in rdf_tripledict[focus_iri].get(RDF.type, ())
]
- _indexcard = Indexcard.objects.filter(
+ _indexcard: Indexcard | None = Indexcard.objects.filter(
source_record_suid=suid,
focus_identifier_set__in=_focus_identifier_set,
).first()
@@ -193,7 +194,7 @@ def latest_resource_description(self) -> LatestResourceDescription:
return self.trove_latestresourcedescription_set.get() # may raise DoesNotExist
@property
- def archived_description_set(self):
+ def archived_description_set(self) -> Any:
'''convenience for the "other side" of ArchivedResourceDescription.indexcard
returns a RelatedManager
@@ -201,17 +202,17 @@ def archived_description_set(self):
return self.trove_archivedresourcedescription_set
@property
- def supplementary_description_set(self):
+ def supplementary_description_set(self) -> Any:
'''convenience for the "other side" of SupplementaryResourceDescription.indexcard
returns a RelatedManager
'''
return self.trove_supplementaryresourcedescription_set
- def get_iri(self):
+ def get_iri(self) -> str:
return trove_indexcard_iri(self.uuid)
- def pls_delete(self, *, notify_indexes=True):
+ def pls_delete(self, *, notify_indexes: bool = True) -> None:
# do not actually delete Indexcard, just mark deleted:
if self.deleted is None:
self.deleted = timezone.now()
@@ -231,10 +232,10 @@ def pls_delete(self, *, notify_indexes=True):
from share.search.index_messenger import IndexMessenger
IndexMessenger().notify_indexcard_update([self])
- def __repr__(self):
+ def __repr__(self) -> str:
return f'<{self.__class__.__qualname__}({self.uuid}, {self.source_record_suid})'
- def __str__(self):
+ def __str__(self) -> str:
return repr(self)
@transaction.atomic
diff --git a/trove/models/resource_description.py b/trove/models/resource_description.py
index 72e690cda..8fcbc1652 100644
--- a/trove/models/resource_description.py
+++ b/trove/models/resource_description.py
@@ -60,10 +60,10 @@ def as_rdfdoc_with_supplements(self) -> rdf.RdfGraph:
_rdfdoc.add_tripledict(_supplement.as_rdf_tripledict())
return _rdfdoc
- def __repr__(self):
- return f'<{self.__class__.__qualname__}({self.id}, "{self.focus_iri}")'
+ def __repr__(self) -> str:
+ return f'<{self.__class__.__qualname__}({self.pk}, "{self.focus_iri}")'
- def __str__(self):
+ def __str__(self) -> str:
return repr(self)
diff --git a/trove/models/resource_identifier.py b/trove/models/resource_identifier.py
index 6d2fe548b..73190b50e 100644
--- a/trove/models/resource_identifier.py
+++ b/trove/models/resource_identifier.py
@@ -1,8 +1,10 @@
+from __future__ import annotations
import typing
from django.core.exceptions import ValidationError
from django.contrib.postgres.fields import ArrayField
from django.db import models
+from django.db.models import QuerySet
from django.db.models.functions import Substr, StrIndex
from primitive_metadata import primitive_rdf
@@ -24,7 +26,7 @@
)
-def validate_iri_scheme(iri_scheme):
+def validate_iri_scheme(iri_scheme: str) -> None:
'''raise a django ValidationError if not a valid iri scheme
'''
if not isinstance(iri_scheme, str):
@@ -33,7 +35,7 @@ def validate_iri_scheme(iri_scheme):
raise ValidationError('not a valid iri scheme')
-def validate_sufficiently_unique_iri(suffuniq_iri: str):
+def validate_sufficiently_unique_iri(suffuniq_iri: str) -> None:
'''raise a django ValidationError if not a valid "sufficiently unique iri"
'''
if not isinstance(suffuniq_iri, str):
@@ -51,21 +53,21 @@ def validate_sufficiently_unique_iri(suffuniq_iri: str):
raise ValidationError('need more iri beyond a scheme')
-class ResourceIdentifierManager(models.Manager):
- def queryset_for_iri(self, iri: str):
+class ResourceIdentifierManager(models.Manager["ResourceIdentifier"]):
+ def queryset_for_iri(self, iri: str) -> QuerySet[ResourceIdentifier]:
return self.queryset_for_iris((iri,))
- def queryset_for_iris(self, iris: typing.Iterable[str]):
+ def queryset_for_iris(self, iris: typing.Iterable[str]) -> QuerySet[ResourceIdentifier]:
# may raise if invalid
_suffuniq_iris = set()
for _iri in iris:
_suffuniq_iris.add(get_sufficiently_unique_iri(_iri))
return self.filter(sufficiently_unique_iri__in=_suffuniq_iris)
- def get_for_iri(self, iri: str) -> 'ResourceIdentifier':
+ def get_for_iri(self, iri: str) -> ResourceIdentifier:
return self.queryset_for_iri(iri).get() # may raise ResourceIdentifier.DoesNotExist
- def get_or_create_for_iri(self, iri: str) -> 'ResourceIdentifier':
+ def get_or_create_for_iri(self, iri: str) -> ResourceIdentifier:
# may raise if invalid
(_suffuniq_iri, _scheme) = get_sufficiently_unique_iri_and_scheme(iri)
(_identifier, _created) = self.get_or_create(
@@ -146,10 +148,10 @@ class Meta:
),
]
- def __repr__(self):
+ def __repr__(self) -> str:
return f'<{self.__class__.__qualname__}({self.id}, "{self.sufficiently_unique_iri}")'
- def __str__(self):
+ def __str__(self) -> str:
return repr(self)
def as_iri(self) -> str:
@@ -176,7 +178,7 @@ def choose_a_scheme(self) -> str:
_scheme = self.scheme_list[0]
return _scheme
- def equivalent_to_iri(self, iri: str):
+ def equivalent_to_iri(self, iri: str) -> bool:
return (self.sufficiently_unique_iri == get_sufficiently_unique_iri(iri))
def find_equivalent_iri(self, tripledict: primitive_rdf.RdfTripleDictionary) -> str:
diff --git a/trove/openapi.py b/trove/openapi.py
index e5b96a442..0ed880583 100644
--- a/trove/openapi.py
+++ b/trove/openapi.py
@@ -1,6 +1,6 @@
import itertools
import json
-from typing import Iterable
+from typing import Iterable, Generator, Any, Tuple
from django.conf import settings
from primitive_metadata import primitive_rdf
@@ -25,7 +25,7 @@ def get_trove_openapi_json() -> str:
return json.dumps(get_trove_openapi(), indent=2)
-def get_trove_openapi() -> dict:
+def get_trove_openapi() -> dict[str, Any]:
'''generate an openapi description of the trove api
following https://spec.openapis.org/oas/v3.1.0
@@ -65,7 +65,7 @@ def get_trove_openapi() -> dict:
}
-def _openapi_parameters(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGraph):
+def _openapi_parameters(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGraph) -> Iterable[tuple[str, Any]]:
_param_iris = set(itertools.chain(*(
api_graph.q(_path_iri, TROVE.hasParameter)
for _path_iri in path_iris
@@ -95,7 +95,7 @@ def _openapi_parameters(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGr
}
-def _openapi_examples(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGraph):
+def _openapi_examples(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGraph) -> Iterable[tuple[str, Any]]:
# assumes examples are blank nodes (frozenset of twoples)
_examples = set(itertools.chain(*(
api_graph.q(_path_iri, TROVE.example)
@@ -129,10 +129,10 @@ def _openapi_examples(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGrap
}
-def _openapi_path(path_iri: str, api_graph: primitive_rdf.RdfGraph):
+def _openapi_path(path_iri: str, api_graph: primitive_rdf.RdfGraph) -> Tuple[str, Any]:
# TODO: better error message on absence
try:
- _path = next(_text(path_iri, TROVE.iriPath, api_graph))
+ _path = next(iter(_text(path_iri, TROVE.iriPath, api_graph)))
except StopIteration:
raise ValueError(f'could not find trove:iriPath for {path_iri}')
_label = ' '.join(_text(path_iri, RDFS.label, api_graph))
@@ -166,7 +166,7 @@ def _openapi_path(path_iri: str, api_graph: primitive_rdf.RdfGraph):
}
-def _concept_markdown_blocks(concept_iri: str, api_graph: primitive_rdf.RdfGraph):
+def _concept_markdown_blocks(concept_iri: str, api_graph: primitive_rdf.RdfGraph) -> Generator[str, None, None]:
for _label in api_graph.q(concept_iri, RDFS.label):
yield f'## {_label.unicode_value}'
for _comment in api_graph.q(concept_iri, RDFS.comment):
@@ -175,12 +175,12 @@ def _concept_markdown_blocks(concept_iri: str, api_graph: primitive_rdf.RdfGraph
yield _desc.unicode_value
-def _text(subj, pred, api_graph):
+def _text(subj: Any, pred: Any, api_graph: primitive_rdf.RdfGraph) -> Iterable[str]:
for _obj in api_graph.q(subj, pred):
yield _obj.unicode_value
-def _markdown_description(subj_iri: str, api_graph: primitive_rdf.RdfGraph):
+def _markdown_description(subj_iri: str, api_graph: primitive_rdf.RdfGraph) -> str:
return '\n\n'.join((
*(
_description.unicode_value
diff --git a/trove/render/__init__.py b/trove/render/__init__.py
index 2e1350ac4..c5bf699a1 100644
--- a/trove/render/__init__.py
+++ b/trove/render/__init__.py
@@ -1,3 +1,5 @@
+from typing import Type
+
from django import http
from trove import exceptions as trove_exceptions
@@ -23,6 +25,10 @@
TrovesearchSimpleTsvRenderer,
)
+RendersType = Type[
+ BaseRenderer | RdfHtmlBrowseRenderer | RdfJsonapiRenderer | RdfTurtleRenderer | RdfJsonldRenderer | TrovesearchSimpleCsvRenderer | TrovesearchSimpleJsonRenderer | TrovesearchSimpleTsvRenderer
+]
+
RENDERER_BY_MEDIATYPE = {
_renderer_type.MEDIATYPE: _renderer_type
for _renderer_type in RENDERERS
diff --git a/trove/render/_base.py b/trove/render/_base.py
index 48cfe1cc8..49a3a52ec 100644
--- a/trove/render/_base.py
+++ b/trove/render/_base.py
@@ -35,16 +35,16 @@ class BaseRenderer(abc.ABC):
thesaurus_tripledict: rdf.RdfTripleDictionary = dataclasses.field(default_factory=lambda: TROVE_API_THESAURUS)
@classmethod
- def get_deriver_iri(cls, card_blending: bool):
+ def get_deriver_iri(cls, card_blending: bool) -> str | None:
# override if needed
return cls.INDEXCARD_DERIVER_IRI
@functools.cached_property
- def thesaurus(self):
+ def thesaurus(self) -> 'rdf.RdfGraph':
return rdf.RdfGraph(self.thesaurus_tripledict)
@functools.cached_property
- def response_data(self):
+ def response_data(self) -> 'rdf.RdfGraph':
return rdf.RdfGraph(self.response_tripledict)
@functools.cached_property
diff --git a/trove/render/_html.py b/trove/render/_html.py
index 45f775880..6daa1e037 100644
--- a/trove/render/_html.py
+++ b/trove/render/_html.py
@@ -1,10 +1,12 @@
from __future__ import annotations
+from collections.abc import Generator
import contextlib
import dataclasses
from xml.etree.ElementTree import (
Element,
SubElement,
)
+from typing import Any
from primitive_metadata import primitive_rdf as rdf
@@ -19,7 +21,7 @@ class HtmlBuilder:
_nested_elements: list[Element] = dataclasses.field(default_factory=list)
_heading_depth: int = 0
- def __post_init__(self):
+ def __post_init__(self) -> None:
self._nested_elements.append(self.given_root)
@property
@@ -34,7 +36,7 @@ def _current_element(self) -> Element:
# html-building helper methods
@contextlib.contextmanager
- def nest_h_tag(self, **kwargs):
+ def nest_h_tag(self, **kwargs: Any) -> Generator[Element]:
_outer_heading_depth = self._heading_depth
if not _outer_heading_depth:
self._heading_depth = 1
@@ -48,7 +50,7 @@ def nest_h_tag(self, **kwargs):
self._heading_depth = _outer_heading_depth
@contextlib.contextmanager
- def nest(self, tag_name, attrs=None):
+ def nest(self, tag_name: str, attrs: dict | None = None) -> Generator[Element]:
_attrs = {**attrs} if attrs else {}
_nested_element = SubElement(self._current_element, tag_name, _attrs)
self._nested_elements.append(_nested_element)
@@ -58,7 +60,7 @@ def nest(self, tag_name, attrs=None):
_popped_element = self._nested_elements.pop()
assert _popped_element is _nested_element
- def leaf(self, tag_name, *, text=None, attrs=None):
+ def leaf(self, tag_name: str, *, text: str | None = None, attrs: dict | None = None) -> None:
_leaf_element = SubElement(self._current_element, tag_name, attrs or {})
if isinstance(text, rdf.Literal):
# TODO: lang
diff --git a/trove/render/_rendering.py b/trove/render/_rendering.py
index 52e5f9e2c..0de9b015a 100644
--- a/trove/render/_rendering.py
+++ b/trove/render/_rendering.py
@@ -1,6 +1,6 @@
import abc
import dataclasses
-from typing import Iterator
+from typing import Iterator, Generator
from trove import exceptions as trove_exceptions
@@ -30,7 +30,7 @@ class SimpleRendering: # implements ProtoRendering
mediatype: str
rendered_content: str = ''
- def iter_content(self):
+ def iter_content(self) -> Generator[str]:
yield self.rendered_content
@@ -40,7 +40,7 @@ class StreamableRendering: # implements ProtoRendering
content_stream: Iterator[str | bytes | memoryview]
_started_already: bool = False
- def iter_content(self):
+ def iter_content(self) -> Iterator[str | bytes | memoryview]:
if self._started_already:
raise trove_exceptions.CannotRenderStreamTwice
self._started_already = True
diff --git a/trove/render/_simple_trovesearch.py b/trove/render/_simple_trovesearch.py
index 6e6ba6eb1..36bc36c4b 100644
--- a/trove/render/_simple_trovesearch.py
+++ b/trove/render/_simple_trovesearch.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+from collections.abc import Generator, Iterator
import json
-from typing import Iterator, Any
+from typing import Any, TYPE_CHECKING
from primitive_metadata import primitive_rdf as rdf
@@ -8,6 +10,8 @@
from trove.vocab.namespaces import TROVE, RDF
from ._base import BaseRenderer
from ._rendering import ProtoRendering, SimpleRendering
+if TYPE_CHECKING:
+ from trove.util.json import JsonObject
class SimpleTrovesearchRenderer(BaseRenderer):
@@ -16,22 +20,22 @@ class SimpleTrovesearchRenderer(BaseRenderer):
(very entangled with trove/trovesearch/trovesearch_gathering.py)
'''
PASSIVE_RENDER = False # knows the properties it cares about
- _page_links: set
+ _page_links: set[str]
__already_iterated_cards = False
- def simple_unicard_rendering(self, card_iri: str, osfmap_json: dict) -> str:
+ def simple_unicard_rendering(self, card_iri: str, osfmap_json: JsonObject) -> str:
raise NotImplementedError
- def simple_multicard_rendering(self, cards: Iterator[tuple[str, dict]]) -> str:
+ def simple_multicard_rendering(self, cards: Iterator[tuple[str, JsonObject]]) -> str:
raise NotImplementedError
- def unicard_rendering(self, card_iri: str, osfmap_json: dict) -> ProtoRendering:
+ def unicard_rendering(self, card_iri: str, osfmap_json: JsonObject) -> ProtoRendering:
return SimpleRendering( # type: ignore[return-value]
mediatype=self.MEDIATYPE,
rendered_content=self.simple_unicard_rendering(card_iri, osfmap_json),
)
- def multicard_rendering(self, card_pages: Iterator[dict[str, dict]]) -> ProtoRendering:
+ def multicard_rendering(self, card_pages: Iterator[dict[str, JsonObject]]) -> ProtoRendering:
_cards = (
(_card_iri, _card_contents)
for _page in card_pages
@@ -53,7 +57,7 @@ def render_document(self) -> ProtoRendering:
)
raise trove_exceptions.UnsupportedRdfType(_focustypes)
- def _iter_card_pages(self) -> Iterator[dict[str, Any]]:
+ def _iter_card_pages(self) -> Generator[dict[str, JsonObject]]:
assert not self.__already_iterated_cards
self.__already_iterated_cards = True
self._page_links = set()
@@ -87,7 +91,7 @@ def _get_card_content(
self,
card: str | rdf.RdfBlanknode,
graph: rdf.RdfGraph | None = None,
- ) -> dict:
+ ) -> Any:
if isinstance(card, str):
_card_content = (
next(self.response_gathering.ask(TROVE.resourceMetadata, focus=card))
diff --git a/trove/render/html_browse.py b/trove/render/html_browse.py
index 9fef803dd..1f5bffd6f 100644
--- a/trove/render/html_browse.py
+++ b/trove/render/html_browse.py
@@ -1,4 +1,7 @@
-from collections.abc import Iterator
+from collections.abc import (
+ Iterator,
+ Generator,
+)
import contextlib
import dataclasses
import datetime
@@ -68,7 +71,7 @@ class RdfHtmlBrowseRenderer(BaseRenderer):
__hb: HtmlBuilder = dataclasses.field(init=False)
__last_hue_turn: float = dataclasses.field(default_factory=random.random)
- def __post_init__(self):
+ def __post_init__(self) -> None:
# TODO: lang (according to request -- also translate)
self.__current_data = self.response_tripledict
self.__visiting_iris = set()
@@ -94,37 +97,37 @@ def simple_render_document(self) -> str:
etree_tostring(self.__hb.root_element, encoding='unicode', method='html'),
))
- def render_html_head(self):
+ def render_html_head(self) -> None:
with self.__hb.nest('head'):
self.__hb.leaf('link', attrs={
'rel': 'stylesheet',
'href': staticfiles_storage.url('css/browse.css'),
})
- def render_nav(self):
+ def render_nav(self) -> None:
with self.__hb.nest('nav'):
self.__alternate_mediatypes_card()
if self.is_data_blended is not None:
self.__blender_toggle_card()
- def render_main(self):
+ def render_main(self) -> None:
with self.__hb.nest('main'):
for _iri in self.response_focus.iris:
self.__render_subj(_iri)
# TODO: show additional unvisited triples?
- def render_footer(self):
+ def render_footer(self) -> None:
with self.__hb.nest('footer'):
...
- def __alternate_mediatypes_card(self):
+ def __alternate_mediatypes_card(self) -> None:
with self.__nest_card('details'):
self.__hb.leaf('summary', text=_('alternate mediatypes'))
for _mediatype in shuffled((*STABLE_MEDIATYPES, *UNSTABLE_MEDIATYPES)):
with self.__hb.nest('span', attrs={'class': 'Browse__literal'}):
self.__mediatype_link(_mediatype)
- def __blender_toggle_card(self):
+ def __blender_toggle_card(self) -> None:
with self.__nest_card('details'):
if self.is_data_blended:
_header_text = _('card-blending ON')
@@ -139,7 +142,7 @@ def __blender_toggle_card(self):
'href': self._queryparam_href('blendCards', _link_blend),
})
- def __mediatype_link(self, mediatype: str):
+ def __mediatype_link(self, mediatype: str) -> None:
self.__hb.leaf('a', text=mediatype, attrs={
'href': self._queryparam_href('acceptMediatype', mediatype),
})
@@ -150,7 +153,7 @@ def __mediatype_link(self, mediatype: str):
with self.__hb.nest('a', attrs={'href': reverse('trove:docs')}) as _link:
_link.text = _('(stable for documented use)')
- def __render_subj(self, subj_iri: str, *, start_collapsed=None):
+ def __render_subj(self, subj_iri: str, *, start_collapsed: bool | None = None) -> None:
_twopledict = self.__current_data.get(subj_iri, {})
with self.__visiting(subj_iri):
with self.__nest_card('article'):
@@ -184,7 +187,7 @@ def __render_subj(self, subj_iri: str, *, start_collapsed=None):
self.__hb.leaf('summary', text=_('more details...'))
self.__twoples(_twopledict)
- def __twoples(self, twopledict: rdf.RdfTwopleDictionary):
+ def __twoples(self, twopledict: rdf.RdfTwopleDictionary) -> None:
with self.__hb.nest('dl', {'class': 'Browse__twopleset'}):
for _pred, _obj_set in shuffled(twopledict.items()):
with self.__hb.nest('dt', attrs={'class': 'Browse__predicate'}):
@@ -195,7 +198,7 @@ def __twoples(self, twopledict: rdf.RdfTwopleDictionary):
for _obj in shuffled(_obj_set):
self.__obj(_obj)
- def __obj(self, obj: rdf.RdfObject):
+ def __obj(self, obj: rdf.RdfObject) -> None:
if isinstance(obj, str): # iri
# TODO: detect whether indexcard?
if (obj in self.__current_data) and (obj not in self.__visiting_iris):
@@ -220,7 +223,7 @@ def __literal(
literal: rdf.Literal | str,
*,
is_rdf_object: bool = False,
- ):
+ ) -> None:
_lit = (literal if isinstance(literal, rdf.Literal) else rdf.literal(literal))
_markdown_iri = rdf.iri_from_mediatype('text/markdown')
_is_markdown = any(
@@ -241,7 +244,7 @@ def __literal(
else:
self.__hb.leaf('q', text=_lit)
- def __sequence(self, sequence_twoples: frozenset):
+ def __sequence(self, sequence_twoples: frozenset[rdf.RdfTwople]) -> None:
_obj_in_order = list(rdf.sequence_objects_in_order(sequence_twoples))
with self.__hb.nest('details', attrs={'open': '', 'class': 'Browse__blanknode Browse__object'}):
_text = _('sequence of %(count)s') % {'count': len(_obj_in_order)}
@@ -251,11 +254,11 @@ def __sequence(self, sequence_twoples: frozenset):
with self.__hb.nest('li'): # , visible=True):
self.__obj(_seq_obj)
- def __quoted_graph(self, quoted_graph: rdf.QuotedGraph):
+ def __quoted_graph(self, quoted_graph: rdf.QuotedGraph) -> None:
with self.__quoted_data(quoted_graph.tripledict):
self.__render_subj(quoted_graph.focus_iri) # , start_collapsed=True)
- def __blanknode(self, blanknode: rdf.RdfTwopleDictionary | frozenset):
+ def __blanknode(self, blanknode: rdf.RdfTwopleDictionary | frozenset) -> None:
_twopledict = (
blanknode
if isinstance(blanknode, dict)
@@ -269,11 +272,11 @@ def __blanknode(self, blanknode: rdf.RdfTwopleDictionary | frozenset):
self.__hb.leaf('summary', text='(blank node)')
self.__twoples(_twopledict)
- def __split_iri_pre(self, iri: str):
+ def __split_iri_pre(self, iri: str) -> None:
self.__hb.leaf('pre', text='\n'.join(self.__iri_lines(iri)))
@contextlib.contextmanager
- def __visiting(self, iri: str):
+ def __visiting(self, iri: str) -> Iterator[None]:
assert iri not in self.__visiting_iris
self.__visiting_iris.add(iri)
try:
@@ -282,7 +285,7 @@ def __visiting(self, iri: str):
self.__visiting_iris.remove(iri)
@contextlib.contextmanager
- def __quoted_data(self, quoted_data: dict):
+ def __quoted_data(self, quoted_data: dict) -> Generator[None]:
_outer_data = self.__current_data
_outer_visiting_iris = self.__visiting_iris
self.__current_data = quoted_data
@@ -293,12 +296,12 @@ def __quoted_data(self, quoted_data: dict):
self.__current_data = _outer_data
self.__visiting_iris = _outer_visiting_iris
- def __iri_link_and_labels(self, iri: str):
+ def __iri_link_and_labels(self, iri: str) -> None:
self.__compact_link(iri)
for _text in self.__iri_thesaurus_labels(iri):
self.__literal(_text)
- def __nest_link(self, iri: str):
+ def __nest_link(self, iri: str) -> contextlib.AbstractContextManager[Element]:
_href = (
iri
if _is_local_url(iri)
@@ -306,12 +309,12 @@ def __nest_link(self, iri: str):
)
return self.__hb.nest('a', attrs={'href': _href})
- def __compact_link(self, iri: str):
+ def __compact_link(self, iri: str) -> Element:
with self.__nest_link(iri) as _a:
_a.text = self.iri_shorthand.compact_iri(iri)
return _a
- def __nest_card(self, tag: str):
+ def __nest_card(self, tag: str) -> contextlib.AbstractContextManager[Element]:
return self.__hb.nest(
tag,
attrs={
@@ -320,7 +323,7 @@ def __nest_card(self, tag: str):
},
)
- def __iri_thesaurus_labels(self, iri: str):
+ def __iri_thesaurus_labels(self, iri: str) -> list[str]:
# TODO: consider requested language
_labels: set[rdf.RdfObject] = set()
_suffuniq = get_sufficiently_unique_iri(iri)
@@ -334,12 +337,12 @@ def __iri_thesaurus_labels(self, iri: str):
_labels.update(_twoples.get(_pred, ()))
return shuffled(_labels)
- def _hue_turn_css(self):
+ def _hue_turn_css(self) -> str:
_hue_turn = (self.__last_hue_turn + _PHI) % 1.0
self.__last_hue_turn = _hue_turn
return f'--hue-turn: {_hue_turn}turn;'
- def _queryparam_href(self, param_name: str, param_value: str | None):
+ def _queryparam_href(self, param_name: str, param_value: str | None) -> str:
_base_url = self.response_focus.single_iri()
if not _is_local_url(_base_url):
_base_url = trove_browse_link(_base_url)
@@ -383,7 +386,7 @@ def __iri_lines(self, iri: str) -> Iterator[str]:
yield f'#{_fragment}'
-def _append_class(el: Element, element_class: str):
+def _append_class(el: Element, element_class: str) -> None:
el.set(
'class',
' '.join(filter(None, (element_class, el.get('class')))),
diff --git a/trove/render/jsonapi.py b/trove/render/jsonapi.py
index 6337e7edc..e60fc2338 100644
--- a/trove/render/jsonapi.py
+++ b/trove/render/jsonapi.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
import base64
from collections import defaultdict
import contextlib
@@ -6,8 +7,9 @@
import itertools
import json
import time
-from typing import Iterable, Union
+from typing import Iterable, Union, List, Any, Dict, Tuple, Iterator
+from typing import Optional
from primitive_metadata import primitive_rdf
from trove import exceptions as trove_exceptions
@@ -31,14 +33,14 @@
# a jsonapi resource may pull rdf data using an iri or blank node
# (using conventions from py for rdf as python primitives)
-_IriOrBlanknode = Union[str, frozenset]
+_IriOrBlanknode = Union[str, frozenset[Any]]
-def _resource_ids_defaultdict():
+def _resource_ids_defaultdict() -> defaultdict[Any, str]:
_prefix = str(time.time_ns())
_ints = itertools.count()
- def _iter_ids():
+ def _iter_ids() -> Iterator[str]:
while True:
_id = next(_ints)
yield f'{_prefix}-{_id}'
@@ -69,17 +71,17 @@ class RdfJsonapiRenderer(BaseRenderer):
MEDIATYPE = mediatypes.JSONAPI
INDEXCARD_DERIVER_IRI = TROVE['derive/osfmap_json']
- _identifier_object_cache: dict = dataclasses.field(default_factory=dict)
+ _identifier_object_cache: dict[str | frozenset[_IriOrBlanknode], Any] = dataclasses.field(default_factory=dict)
_id_namespace_set: Iterable[primitive_rdf.IriNamespace] = (trove_indexcard_namespace(),)
__to_include: set[primitive_rdf.RdfObject] | None = None
- __assigned_blanknode_resource_ids: defaultdict[frozenset, str] = dataclasses.field(
+ __assigned_blanknode_resource_ids: defaultdict[frozenset[_IriOrBlanknode], str] = dataclasses.field(
default_factory=_resource_ids_defaultdict,
repr=False,
)
# override BaseRenderer
@classmethod
- def get_deriver_iri(cls, card_blending: bool):
+ def get_deriver_iri(cls, card_blending: bool) -> str | None:
return (None if card_blending else super().get_deriver_iri(card_blending))
def simple_render_document(self) -> str:
@@ -88,7 +90,7 @@ def simple_render_document(self) -> str:
indent=2, # TODO: pretty-print query param?
)
- def render_dict(self, primary_iris: Union[str, Iterable[str]]) -> dict:
+ def render_dict(self, primary_iris: Union[str, Iterable[str]]) -> dict[str, Any]:
_primary_data: dict | list | None = None
_included_data = []
with self._contained__to_include() as _to_include:
@@ -111,7 +113,7 @@ def render_dict(self, primary_iris: Union[str, Iterable[str]]) -> dict:
_document['included'] = _included_data
return _document
- def render_resource_object(self, iri_or_blanknode: _IriOrBlanknode) -> dict:
+ def render_resource_object(self, iri_or_blanknode: _IriOrBlanknode) -> dict[str, Any]:
_resource_object = {**self.render_identifier_object(iri_or_blanknode)}
_twopledict = (
(self.response_data.tripledict.get(iri_or_blanknode) or {})
@@ -125,7 +127,7 @@ def render_resource_object(self, iri_or_blanknode: _IriOrBlanknode) -> dict:
_resource_object.setdefault('links', {})['self'] = iri_or_blanknode
return _resource_object
- def render_identifier_object(self, iri_or_blanknode: _IriOrBlanknode):
+ def render_identifier_object(self, iri_or_blanknode: _IriOrBlanknode) -> Any | dict[str, Any]:
try:
return self._identifier_object_cache[iri_or_blanknode]
except KeyError:
@@ -154,7 +156,7 @@ def render_identifier_object(self, iri_or_blanknode: _IriOrBlanknode):
self._identifier_object_cache[iri_or_blanknode] = _id_obj
return _id_obj
- def _single_typename(self, type_iris: list[str]):
+ def _single_typename(self, type_iris: list[str]) -> Optional[str]:
if not type_iris:
return ''
if len(type_iris) == 1:
@@ -166,7 +168,7 @@ def _single_typename(self, type_iris: list[str]):
return self._membername_for_iri(_type_iris[0])
return self._membername_for_iri(sorted(type_iris)[0])
- def _membername_for_iri(self, iri: str):
+ def _membername_for_iri(self, iri: str) -> Optional[str] | Any:
try:
_membername = next(self.thesaurus.q(iri, JSONAPI_MEMBERNAME))
except StopIteration:
@@ -177,10 +179,10 @@ def _membername_for_iri(self, iri: str):
raise trove_exceptions.ExpectedLiteralObject((iri, JSONAPI_MEMBERNAME, _membername))
return self.iri_shorthand.compact_iri(iri)
- def _resource_id_for_blanknode(self, blanknode: frozenset, /):
+ def _resource_id_for_blanknode(self, blanknode: frozenset[Any]) -> str:
return self.__assigned_blanknode_resource_ids[blanknode]
- def _resource_id_for_iri(self, iri: str):
+ def _resource_id_for_iri(self, iri: str) -> Any:
for _iri_namespace in self._id_namespace_set:
if iri in _iri_namespace:
return primitive_rdf.iri_minus_namespace(iri, namespace=_iri_namespace)
@@ -191,12 +193,12 @@ def _resource_id_for_iri(self, iri: str):
# as fallback, encode the iri into a valid jsonapi member name
return base64.urlsafe_b64encode(iri.encode()).decode()
- def _render_field(self, predicate_iri, object_set, *, into: dict):
+ def _render_field(self, predicate_iri: str, object_set: Iterable[Any], *, into: dict[str, Any]) -> None:
_is_relationship = (predicate_iri, RDF.type, JSONAPI_RELATIONSHIP) in self.thesaurus
_is_attribute = (predicate_iri, RDF.type, JSONAPI_ATTRIBUTE) in self.thesaurus
_field_key = self._membername_for_iri(predicate_iri)
_doc_key = 'meta' # unless configured for jsonapi, default to unstructured 'meta'
- if ':' not in _field_key:
+ if ':' not in _field_key: # type: ignore
if _is_relationship:
_doc_key = 'relationships'
elif _is_attribute:
@@ -204,25 +206,29 @@ def _render_field(self, predicate_iri, object_set, *, into: dict):
if _is_relationship:
_fieldvalue = self._render_relationship_object(predicate_iri, object_set)
else:
- _fieldvalue = self._one_or_many(predicate_iri, self._attribute_datalist(object_set))
+ _fieldvalue = self._one_or_many(predicate_iri, self._attribute_datalist(object_set)) # type: ignore
# update the given `into` resource object
into.setdefault(_doc_key, {})[_field_key] = _fieldvalue
- def _one_or_many(self, predicate_iri: str, datalist: list):
+ def _one_or_many(self, predicate_iri: str, datalist: list[Any]) -> Union[list[Any], Any, None]:
_only_one = (predicate_iri, RDF.type, OWL.FunctionalProperty) in self.thesaurus
if _only_one:
if len(datalist) > 1:
raise trove_exceptions.OwlObjection(f'multiple objects for to-one relation <{predicate_iri}>: {datalist}')
- return (datalist[0] if datalist else None)
+ return datalist[0] if datalist else None
return datalist
- def _attribute_datalist(self, object_set):
+ def _attribute_datalist(self, object_set: Iterable[Any]) -> List[Any]:
return [
self._render_attribute_datum(_obj)
for _obj in object_set
]
- def _render_relationship_object(self, predicate_iri, object_set):
+ def _render_relationship_object(
+ self,
+ predicate_iri: str,
+ object_set: Iterable[Union[frozenset[Any], str]]
+ ) -> Dict[str, Any]:
_data = []
_links = {}
for _obj in object_set:
@@ -248,7 +254,7 @@ def _render_relationship_object(self, predicate_iri, object_set):
_relationship_obj['links'] = _links
return _relationship_obj
- def _render_link_object(self, link_obj: frozenset):
+ def _render_link_object(self, link_obj: frozenset[Tuple[Any, Any]]) -> Tuple[str, Dict[str, Any]]:
_membername = next(
_obj.unicode_value
for _pred, _obj in link_obj
@@ -270,7 +276,7 @@ def _render_link_object(self, link_obj: frozenset):
}
return _membername, _rendered_link
- def _make_object_gen(self, object_set):
+ def _make_object_gen(self, object_set: frozenset[Any]) -> Iterator[Any]:
for _obj in object_set:
if isinstance(_obj, frozenset) and ((RDF.type, RDF.Seq) in _obj):
yield from primitive_rdf.sequence_objects_in_order(_obj)
@@ -278,7 +284,7 @@ def _make_object_gen(self, object_set):
yield _obj
@contextlib.contextmanager
- def _contained__to_include(self):
+ def _contained__to_include(self) -> Iterator[set[primitive_rdf.RdfObject]]:
assert self.__to_include is None
self.__to_include = set()
try:
@@ -286,11 +292,11 @@ def _contained__to_include(self):
finally:
self.__to_include = None
- def _pls_include(self, item):
+ def _pls_include(self, item: Any) -> None:
if self.__to_include is not None:
self.__to_include.add(item)
- def _render_attribute_datum(self, rdfobject: primitive_rdf.RdfObject) -> dict | list | str | float | int:
+ def _render_attribute_datum(self, rdfobject: primitive_rdf.RdfObject) -> dict[Any, Any] | list[Any] | str | float | int:
if isinstance(rdfobject, frozenset):
if (RDF.type, RDF.Seq) in rdfobject:
return [
diff --git a/trove/render/jsonld.py b/trove/render/jsonld.py
index 9ac61554b..a7ca263c6 100644
--- a/trove/render/jsonld.py
+++ b/trove/render/jsonld.py
@@ -1,6 +1,8 @@
+from __future__ import annotations
import contextlib
import datetime
import json
+from typing import Any, Iterator, TYPE_CHECKING
from primitive_metadata import primitive_rdf as rdf
@@ -8,6 +10,11 @@
from trove.vocab.namespaces import RDF, OWL, TROVE
from trove.vocab import mediatypes
from ._base import BaseRenderer
+if TYPE_CHECKING:
+ from trove.util.json import (
+ JsonObject,
+ JsonValue,
+ )
_PREDICATES_OF_FLEXIBLE_CARDINALITY = {
@@ -20,7 +27,7 @@ class RdfJsonldRenderer(BaseRenderer):
MEDIATYPE = mediatypes.JSONLD
INDEXCARD_DERIVER_IRI = TROVE['derive/osfmap_json']
- __visiting_iris: set | None = None
+ __visiting_iris: set[str] | None = None
def simple_render_document(self) -> str:
return json.dumps(
@@ -34,7 +41,7 @@ def render_jsonld(
rdfgraph: rdf.RdfGraph,
focus_iri: str,
with_context: bool = False,
- ) -> dict:
+ ) -> JsonObject:
with self.iri_shorthand.track_used_shorts() as _used_shorts:
_rendered = self.rdfobject_as_jsonld(focus_iri, rdfgraph.tripledict)
if with_context:
@@ -44,7 +51,7 @@ def render_jsonld(
}
return _rendered
- def literal_as_jsonld(self, rdfliteral: rdf.Literal):
+ def literal_as_jsonld(self, rdfliteral: rdf.Literal) -> JsonObject:
if not rdfliteral.datatype_iris or rdfliteral.datatype_iris == {RDF.string}:
return {'@value': rdfliteral.unicode_value}
if RDF.JSON in rdfliteral.datatype_iris:
@@ -74,7 +81,7 @@ def rdfobject_as_jsonld(
self,
rdfobject: rdf.RdfObject,
tripledict: rdf.RdfTripleDictionary | None = None,
- ):
+ ) -> JsonObject:
if isinstance(rdfobject, str):
return self.iri_as_jsonld(rdfobject, tripledict)
elif isinstance(rdfobject, frozenset):
@@ -95,7 +102,7 @@ def blanknode_as_jsonld(
self,
blanknode: rdf.RdfBlanknode,
tripledict: rdf.RdfTripleDictionary | None = None,
- ) -> dict:
+ ) -> JsonObject:
_twopledict = rdf.twopledict_from_twopleset(blanknode)
_jsonld = {}
for _pred, _objectset in _twopledict.items():
@@ -111,9 +118,9 @@ def iri_as_jsonld(
self,
iri: str,
tripledict: rdf.RdfTripleDictionary | None = None,
- ):
+ ) -> JsonObject:
if (not tripledict) or (iri not in tripledict) or self.__already_visiting(iri):
- return self.iri_shorthand.compact_iri(iri)
+ return {'@id': self.iri_shorthand.compact_iri(iri)}
with self.__visiting(iri):
_nested_obj = (
{}
@@ -131,7 +138,7 @@ def iri_as_jsonld(
)
return _nested_obj
- def _list_or_single_value(self, predicate_iri: str, objectlist: list):
+ def _list_or_single_value(self, predicate_iri: str, objectlist: list[JsonValue]) -> JsonValue:
_only_one_object = (
(predicate_iri, RDF.type, OWL.FunctionalProperty) in self.thesaurus
)
@@ -152,7 +159,7 @@ def _list_or_single_value(self, predicate_iri: str, objectlist: list):
return sorted(objectlist, key=_naive_sort_key)
@contextlib.contextmanager
- def __visiting(self, iri: str):
+ def __visiting(self, iri: str) -> Iterator[None]:
if self.__visiting_iris is None:
self.__visiting_iris = set()
self.__visiting_iris.add(iri)
@@ -163,6 +170,6 @@ def __already_visiting(self, iri: str) -> bool:
return bool(self.__visiting_iris and (iri in self.__visiting_iris))
-def _naive_sort_key(jsonable_obj):
+def _naive_sort_key(jsonable_obj: Any) -> tuple[int, str]:
_json = json.dumps(jsonable_obj)
- return (len(_json), _json)
+ return len(_json), _json
diff --git a/trove/render/simple_csv.py b/trove/render/simple_csv.py
index dfca6e30c..52c9d700b 100644
--- a/trove/render/simple_csv.py
+++ b/trove/render/simple_csv.py
@@ -1,13 +1,15 @@
from __future__ import annotations
from collections.abc import (
- Iterable,
+ Generator,
Iterator,
+ Iterable,
+ Sequence,
)
import csv
import functools
import itertools
import dataclasses
-import typing
+from typing import TYPE_CHECKING, ClassVar
from trove.trovesearch.search_params import (
CardsearchParams,
@@ -18,12 +20,14 @@
from trove.vocab import osfmap
from trove.vocab.namespaces import TROVE
from ._simple_trovesearch import SimpleTrovesearchRenderer
-from ._rendering import StreamableRendering
-if typing.TYPE_CHECKING:
+from ._rendering import StreamableRendering, ProtoRendering
+if TYPE_CHECKING:
from trove.util.trove_params import BasicTroveParams
+ from trove.util.json import JsonValue, JsonObject
-Jsonpath = Iterable[str] # path of json keys
+type Jsonpath = Sequence[str] # path of json keys
+type CsvValue = str | int | float | None
_MULTIVALUE_DELIMITER = ' ; ' # possible improvement: smarter in-value delimiting?
_VALUE_KEY_PREFERENCE = ('@value', '@id', 'name', 'prefLabel', 'label')
@@ -33,23 +37,27 @@
class TrovesearchSimpleCsvRenderer(SimpleTrovesearchRenderer):
MEDIATYPE = mediatypes.CSV
INDEXCARD_DERIVER_IRI = TROVE['derive/osfmap_json']
- CSV_DIALECT = csv.excel
+ CSV_DIALECT: ClassVar[type[csv.Dialect]] = csv.excel
- def unicard_rendering(self, card_iri: str, osfmap_json: dict):
- self.multicard_rendering(card_pages=iter([{card_iri: osfmap_json}]))
+ def unicard_rendering(self, card_iri: str, osfmap_json: JsonObject) -> ProtoRendering:
+ return self.multicard_rendering(card_pages=iter([{card_iri: osfmap_json}]))
- def multicard_rendering(self, card_pages: Iterator[dict[str, dict]]):
+ def multicard_rendering(self, card_pages: Iterator[dict[str, JsonObject]]) -> ProtoRendering:
_doc = TabularDoc(
card_pages,
trove_params=getattr(self.response_focus, 'search_params', None),
)
- return StreamableRendering(
+ return StreamableRendering( # type: ignore[return-value]
mediatype=self.MEDIATYPE,
content_stream=csv_stream(self.CSV_DIALECT, _doc.header(), _doc.rows()),
)
-def csv_stream(csv_dialect, header: list, rows: Iterator[list]) -> Iterator[str]:
+def csv_stream(
+ csv_dialect: type[csv.Dialect],
+ header: list[CsvValue],
+ rows: Iterator[list[CsvValue]],
+) -> Iterator[str]:
_writer = csv.writer(_Echo(), dialect=csv_dialect)
yield _writer.writerow(header)
for _row in rows:
@@ -58,7 +66,7 @@ def csv_stream(csv_dialect, header: list, rows: Iterator[list]) -> Iterator[str]
@dataclasses.dataclass
class TabularDoc:
- card_pages: Iterator[dict[str, dict]]
+ card_pages: Iterator[dict[str, JsonObject]]
trove_params: BasicTroveParams | None = None
_started: bool = False
@@ -71,11 +79,11 @@ def column_jsonpaths(self) -> tuple[Jsonpath, ...]:
return (_ID_JSONPATH, *_column_jsonpaths)
@functools.cached_property
- def first_page(self) -> dict[str, dict]:
+ def first_page(self) -> dict[str, JsonObject]:
return next(self.card_pages, {})
def _column_paths(self) -> Iterator[Propertypath]:
- _pathlists: list[Iterable[Propertypath]] = []
+ _pathlists: list[Sequence[Propertypath]] = []
if self.trove_params is not None: # hacks
if GLOB_PATHSTEP in self.trove_params.attrpaths_by_type:
_pathlists.append(self.trove_params.attrpaths_by_type[GLOB_PATHSTEP])
@@ -97,35 +105,35 @@ def _column_paths(self) -> Iterator[Propertypath]:
return self.iter_unique(itertools.chain.from_iterable(_pathlists))
@staticmethod
- def iter_unique(iterable):
+ def iter_unique[T](iterable: Iterable[T]) -> Generator[T]:
_seen = set()
for _item in iterable:
if _item not in _seen:
_seen.add(_item)
yield _item
- def _iter_card_pages(self):
+ def _iter_card_pages(self) -> Generator[dict[str, JsonObject]]:
assert not self._started
self._started = True
if self.first_page:
yield self.first_page
yield from self.card_pages
- def header(self) -> list[str]:
+ def header(self) -> list[CsvValue]:
return ['.'.join(_path) for _path in self.column_jsonpaths]
- def rows(self) -> Iterator[list[str]]:
+ def rows(self) -> Generator[list[CsvValue]]:
for _page in self._iter_card_pages():
for _card_iri, _osfmap_json in _page.items():
yield self._row_values(_osfmap_json)
- def _row_values(self, osfmap_json: dict) -> list[str]:
+ def _row_values(self, osfmap_json: JsonObject) -> list[CsvValue]:
return [
self._row_field_value(osfmap_json, _field_path)
for _field_path in self.column_jsonpaths
]
- def _row_field_value(self, osfmap_json: dict, field_path: Jsonpath) -> str:
+ def _row_field_value(self, osfmap_json: JsonObject, field_path: Jsonpath) -> CsvValue:
_rendered_values = [
_render_tabularly(_obj)
for _obj in _iter_values(osfmap_json, field_path)
@@ -136,7 +144,7 @@ def _row_field_value(self, osfmap_json: dict, field_path: Jsonpath) -> str:
return _MULTIVALUE_DELIMITER.join(map(str, _rendered_values))
-def _osfmap_jsonpath(iri_path: Iterable[str]) -> Jsonpath:
+def _osfmap_jsonpath(iri_path: Propertypath) -> Jsonpath:
_shorthand = osfmap.osfmap_json_shorthand()
return tuple(
_shorthand.compact_iri(_pathstep)
@@ -144,7 +152,7 @@ def _osfmap_jsonpath(iri_path: Iterable[str]) -> Jsonpath:
)
-def _has_value(osfmap_json: dict, path: Jsonpath) -> bool:
+def _has_value(osfmap_json: JsonObject, path: Jsonpath) -> bool:
try:
next(_iter_values(osfmap_json, path))
except StopIteration:
@@ -153,7 +161,7 @@ def _has_value(osfmap_json: dict, path: Jsonpath) -> bool:
return True
-def _iter_values(osfmap_json: dict, path: Jsonpath) -> Iterator:
+def _iter_values(osfmap_json: JsonObject, path: Jsonpath) -> Generator[JsonValue]:
assert path
(_step, *_rest) = path
_val = osfmap_json.get(_step)
@@ -162,7 +170,8 @@ def _iter_values(osfmap_json: dict, path: Jsonpath) -> Iterator:
yield from _iter_values(_val, _rest)
elif isinstance(_val, list):
for _val_obj in _val:
- yield from _iter_values(_val_obj, _rest)
+ if isinstance(_val_obj, dict):
+ yield from _iter_values(_val_obj, _rest)
else:
if isinstance(_val, list):
yield from _val
@@ -170,7 +179,7 @@ def _iter_values(osfmap_json: dict, path: Jsonpath) -> Iterator:
yield _val
-def _render_tabularly(json_val):
+def _render_tabularly(json_val: JsonValue) -> CsvValue:
if isinstance(json_val, (str, int, float)):
return json_val
if isinstance(json_val, dict):
@@ -183,7 +192,7 @@ def _render_tabularly(json_val):
else None
)
if _val is not None:
- return _val
+ return _render_tabularly(_val)
return None
@@ -192,5 +201,5 @@ class _Echo:
from https://docs.djangoproject.com/en/5.1/howto/outputting-csv/#streaming-large-csv-files
'''
- def write(self, line: str):
+ def write(self, line: str) -> str:
return line
diff --git a/trove/render/simple_json.py b/trove/render/simple_json.py
index 480ef1c7f..753d6ee6e 100644
--- a/trove/render/simple_json.py
+++ b/trove/render/simple_json.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
import json
import re
import typing
@@ -10,8 +11,10 @@
)
from trove.vocab import mediatypes
from trove.vocab.namespaces import TROVE, RDF
-from ._rendering import StreamableRendering
+from ._rendering import StreamableRendering, ProtoRendering
from ._simple_trovesearch import SimpleTrovesearchRenderer
+if typing.TYPE_CHECKING:
+ from trove.util.json import JsonObject
class TrovesearchSimpleJsonRenderer(SimpleTrovesearchRenderer):
@@ -20,20 +23,20 @@ class TrovesearchSimpleJsonRenderer(SimpleTrovesearchRenderer):
MEDIATYPE = mediatypes.JSON
INDEXCARD_DERIVER_IRI = TROVE['derive/osfmap_json']
- def simple_unicard_rendering(self, card_iri, osfmap_json):
+ def simple_unicard_rendering(self, card_iri: str, osfmap_json: dict[str, typing.Any]) -> str:
return json.dumps({
'data': self._render_card_content(card_iri, osfmap_json),
'links': self._render_links(),
'meta': self._render_meta(),
}, indent=2)
- def multicard_rendering(self, card_pages: typing.Iterator[dict[str, dict]]):
- return StreamableRendering(
+ def multicard_rendering(self, card_pages: typing.Iterator[dict[str, dict[str, typing.Any]]]) -> ProtoRendering:
+ return StreamableRendering( # type: ignore[return-value]
mediatype=self.MEDIATYPE,
content_stream=self._stream_json(card_pages),
)
- def _stream_json(self, card_pages: typing.Iterator[dict[str, dict]]):
+ def _stream_json(self, card_pages: typing.Iterator[dict[str, typing.Any]]) -> typing.Generator[str]:
_prefix = '{"data": ['
yield _prefix
_datum_prefix = None
@@ -54,11 +57,11 @@ def _stream_json(self, card_pages: typing.Iterator[dict[str, dict]]):
count=1,
)
- def _render_card_content(self, card_iri: str, osfmap_json: dict):
+ def _render_card_content(self, card_iri: str, osfmap_json: JsonObject) -> JsonObject:
self._add_twople(osfmap_json, 'foaf:isPrimaryTopicOf', card_iri)
return osfmap_json
- def _render_meta(self):
+ def _render_meta(self) -> dict[str, int | str]:
_meta: dict[str, int | str] = {}
try:
_total = next(self.response_gathering.ask(
@@ -75,7 +78,7 @@ def _render_meta(self):
pass
return _meta
- def _render_links(self):
+ def _render_links(self) -> dict[str, typing.Any]:
_links = {}
for _pagelink in self._page_links:
_twopledict = rdf.twopledict_from_twopleset(_pagelink)
@@ -85,7 +88,7 @@ def _render_links(self):
_links[_membername.unicode_value] = _link_url
return _links
- def _add_twople(self, json_dict, predicate_iri: str, object_iri: str):
+ def _add_twople(self, json_dict: dict[str, typing.Any], predicate_iri: str, object_iri: str) -> None:
_obj_ref = {'@id': object_iri}
_obj_list = json_dict.setdefault(predicate_iri, [])
if isinstance(_obj_list, list):
diff --git a/trove/render/simple_tsv.py b/trove/render/simple_tsv.py
index 60eb4023b..30b01a8a6 100644
--- a/trove/render/simple_tsv.py
+++ b/trove/render/simple_tsv.py
@@ -7,4 +7,4 @@
class TrovesearchSimpleTsvRenderer(TrovesearchSimpleCsvRenderer):
MEDIATYPE = mediatypes.TSV
- CSV_DIALECT: type[csv.Dialect] = csv.excel_tab
+ CSV_DIALECT = csv.excel_tab
diff --git a/trove/render/turtle.py b/trove/render/turtle.py
index e8239b34f..869e12472 100644
--- a/trove/render/turtle.py
+++ b/trove/render/turtle.py
@@ -1,3 +1,5 @@
+from typing import Any
+
from primitive_metadata import primitive_rdf as rdf
from trove.vocab.namespaces import TROVE
@@ -9,7 +11,7 @@ class RdfTurtleRenderer(BaseRenderer):
# include indexcard metadata as JSON literals (because QuotedGraph is non-standard)
INDEXCARD_DERIVER_IRI = TROVE['derive/osfmap_json']
- def simple_render_document(self) -> str:
+ def simple_render_document(self) -> Any:
return rdf.turtle_from_tripledict(
self.response_data.tripledict,
focus=self.response_focus.single_iri(),
diff --git a/trove/trovebrowse_gathering.py b/trove/trovebrowse_gathering.py
index 3da36167a..f8efb9a60 100644
--- a/trove/trovebrowse_gathering.py
+++ b/trove/trovebrowse_gathering.py
@@ -1,3 +1,6 @@
+from collections.abc import Generator
+from typing import Any
+
from primitive_metadata import gather
from primitive_metadata import primitive_rdf as rdf
@@ -5,9 +8,10 @@
from trove.util.iris import get_sufficiently_unique_iri
from trove.vocab import namespaces as ns
from trove.vocab import static_vocab
-from trove.vocab.trove import (
- TROVE_API_THESAURUS,
-)
+from trove.vocab.trove import TROVE_API_THESAURUS
+
+
+type GathererGenerator = Generator[rdf.RdfTriple | rdf.RdfTwople]
TROVEBROWSE_NORMS = gather.GatheringNorms.new(
@@ -32,7 +36,7 @@
@trovebrowse.gatherer(ns.FOAF.isPrimaryTopicOf)
-def gather_cards_focused_on(focus, *, blend_cards: bool):
+def gather_cards_focused_on(focus: gather.Focus, *, blend_cards: bool) -> GathererGenerator:
_identifier_qs = trove_db.ResourceIdentifier.objects.queryset_for_iris(focus.iris)
_indexcard_qs = trove_db.Indexcard.objects.filter(focus_identifier_set__in=_identifier_qs)
if blend_cards:
@@ -46,7 +50,7 @@ def gather_cards_focused_on(focus, *, blend_cards: bool):
@trovebrowse.gatherer(ns.TROVE.thesaurusEntry)
-def gather_thesaurus_entry(focus, *, blend_cards: bool):
+def gather_thesaurus_entry(focus: gather.Focus, *, blend_cards: bool) -> GathererGenerator:
_thesaurus = static_vocab.combined_thesaurus__suffuniq()
for _iri in focus.iris:
_suffuniq_iri = get_sufficiently_unique_iri(_iri)
@@ -59,5 +63,5 @@ def gather_thesaurus_entry(focus, *, blend_cards: bool):
@trovebrowse.gatherer(ns.TROVE.usedAtPath)
-def gather_paths_used_at(focus, **kwargs):
+def gather_paths_used_at(focus: gather.Focus, **kwargs: Any) -> GathererGenerator:
yield from () # TODO via elasticsearch aggregation
diff --git a/trove/trovesearch/page_cursor.py b/trove/trovesearch/page_cursor.py
index e5e5ee3ff..5bbdf5ac0 100644
--- a/trove/trovesearch/page_cursor.py
+++ b/trove/trovesearch/page_cursor.py
@@ -7,7 +7,7 @@
import typing
from trove.exceptions import InvalidPageCursorValue
-
+from typing import Any
__all__ = ('PageCursor', 'OffsetCursor', 'ReproduciblyRandomSampleCursor')
@@ -110,17 +110,17 @@ def is_valid(self) -> bool:
def is_first_page(self) -> bool:
return self.start_offset == 0
- def next_cursor(self):
+ def next_cursor(self) -> OffsetCursor | None:
_next = dataclasses.replace(self, start_offset=int(self.start_offset + self.bounded_page_size))
- return (_next if _next.is_valid() else None)
+ return _next if _next.is_valid() else None
- def prev_cursor(self):
+ def prev_cursor(self) -> OffsetCursor | None:
_prev = dataclasses.replace(self, start_offset=int(self.start_offset - self.bounded_page_size))
- return (_prev if _prev.is_valid() else None)
+ return _prev if _prev.is_valid() else None
- def first_cursor(self):
+ def first_cursor(self) -> OffsetCursor | None:
_first = dataclasses.replace(self, start_offset=0)
- return (_first if _first.is_valid() else None)
+ return _first if _first.is_valid() else None
@dataclasses.dataclass
@@ -130,16 +130,16 @@ class ReproduciblyRandomSampleCursor(OffsetCursor):
# start_offset: int (from OffsetCursor)
first_page_ids: list[str] = dataclasses.field(default_factory=list)
- def next_cursor(self):
+ def next_cursor(self) -> ReproduciblyRandomSampleCursor | None:
return (
- super().next_cursor()
+ super().next_cursor() # type: ignore
if self.first_page_ids
else None
)
- def prev_cursor(self):
+ def prev_cursor(self) -> ReproduciblyRandomSampleCursor | None:
return (
- super().prev_cursor()
+ super().prev_cursor() # type: ignore
if self.first_page_ids
else None
)
@@ -149,37 +149,37 @@ def prev_cursor(self):
class SearchAfterCursor(PageCursor):
# page_size: int (from PageCursor)
# total_count: int (from PageCursor)
- search_after: list | None = None
- next_search_after: list | None = None
- prev_search_after: list | None = None
+ search_after: list[Any] | None = None
+ next_search_after: list[Any] | None = None
+ prev_search_after: list[Any] | None = None
def is_first_page(self) -> bool:
return self.search_after is None
- def next_cursor(self):
+ def next_cursor(self) -> SearchAfterCursor | None:
_next = dataclasses.replace(
self,
search_after=self.next_search_after,
next_search_after=None,
)
- return (_next if _next.is_valid() else None)
+ return _next if _next.is_valid() else None
- def prev_cursor(self):
+ def prev_cursor(self) -> SearchAfterCursor | None:
_prev = dataclasses.replace(
self,
search_after=self.prev_search_after,
next_search_after=self.search_after,
)
- return (_prev if _prev.is_valid() else None)
+ return _prev if _prev.is_valid() else None
- def first_cursor(self):
+ def first_cursor(self) -> SearchAfterCursor | None:
_first = dataclasses.replace(
self,
search_after=None,
next_search_after=None,
prev_search_after=None,
)
- return (_first if _first.is_valid() else None)
+ return _first if _first.is_valid() else None
class _PageCursorTypes(enum.Enum):
diff --git a/trove/trovesearch/search_handle.py b/trove/trovesearch/search_handle.py
index 01dbffd84..b3ce4a8f7 100644
--- a/trove/trovesearch/search_handle.py
+++ b/trove/trovesearch/search_handle.py
@@ -39,7 +39,7 @@ class CardsearchHandle(BasicSearchHandle):
search_result_page: typing.Iterable[CardsearchResult] = ()
related_propertypath_results: list[PropertypathUsage] = dataclasses.field(default_factory=list)
- def __post_init__(self):
+ def __post_init__(self): # type: ignore
_cursor = self.cursor
_page = self.search_result_page
if ( # TODO: move this logic into the... cursor?
@@ -96,7 +96,7 @@ class CardsearchResult:
card_pk: str = ''
@property
- def card_uuid(self):
+ def card_uuid(self) -> typing.Any:
# card iri has the uuid at the end
return primitive_rdf.iri_minus_namespace(
self.card_iri,
@@ -104,7 +104,7 @@ def card_uuid(self):
)
@property
- def card_id(self):
+ def card_id(self) -> str:
return self.card_pk or self.card_uuid
@@ -125,7 +125,7 @@ class ValuesearchResult:
match_count: int = 0
total_count: int = 0
- def __post_init__(self):
+ def __post_init__(self) -> None:
assert (self.value_iri is not None) or (self.value_value is not None), (
f'either value_iri or value_value required (on {self})'
)
diff --git a/trove/trovesearch/search_params.py b/trove/trovesearch/search_params.py
index b8bbf34a9..dfe047a49 100644
--- a/trove/trovesearch/search_params.py
+++ b/trove/trovesearch/search_params.py
@@ -1,5 +1,10 @@
from __future__ import annotations
-import collections
+from collections.abc import (
+ Generator,
+ Mapping,
+ Collection,
+ Iterable,
+)
import dataclasses
import enum
import functools
@@ -32,6 +37,8 @@
from trove.vocab import osfmap
from trove.vocab.trove import trove_json_shorthand
from trove.vocab.namespaces import RDF, TROVE, OWL, FOAF, DCTERMS
+if typing.TYPE_CHECKING:
+ from primitive_metadata.primitive_rdf import IriShorthand
logger = logging.getLogger(__name__)
@@ -49,7 +56,7 @@
DEFAULT_PROPERTYPATH_SET: PropertypathSet = frozenset([ONE_GLOB_PROPERTYPATH])
-DEFAULT_INCLUDES_BY_TYPE: collections.abc.Mapping[str, frozenset[Propertypath]] = freeze({
+DEFAULT_INCLUDES_BY_TYPE: Mapping[str, frozenset[Propertypath]] = freeze({
TROVE.Indexcard: set(),
TROVE.Cardsearch: {
(TROVE.searchResultPage,),
@@ -63,7 +70,7 @@
},
})
-DEFAULT_FIELDS_BY_TYPE: collections.abc.Mapping[str, tuple[Propertypath, ...]] = freeze({
+DEFAULT_FIELDS_BY_TYPE: Mapping[str, tuple[Propertypath, ...]] = freeze({
TROVE.Indexcard: [
(TROVE.resourceMetadata,),
(TROVE.focusIdentifier,),
@@ -93,12 +100,12 @@ class ValueType(enum.Enum):
INTEGER = TROVE['value-type/integer']
@classmethod
- def from_shortname(cls, shortname):
+ def from_shortname(cls, shortname: str) -> typing.Self:
_iri = trove_json_shorthand().expand_iri(shortname)
return cls(_iri)
@classmethod
- def shortnames(cls):
+ def shortnames(cls) -> Generator[str]:
for _value_type in cls:
yield _value_type.to_shortname()
@@ -115,15 +122,15 @@ class TrovesearchParams(BasicTroveParams):
static_focus_type: typing.ClassVar[str] # expected on subclasses
@classmethod
- def _default_shorthand(cls): # NOTE: osfmap special
+ def _default_shorthand(cls) -> IriShorthand: # NOTE: osfmap special
return osfmap.osfmap_json_shorthand()
@classmethod
- def _default_include(cls):
+ def _default_include(cls) -> PropertypathSet:
return DEFAULT_INCLUDES_BY_TYPE.get(cls.static_focus_type, frozenset())
@classmethod
- def _default_attrpaths(cls) -> collections.abc.Mapping[str, tuple[Propertypath, ...]]:
+ def _default_attrpaths(cls) -> Mapping[str, tuple[Propertypath, ...]]:
return DEFAULT_FIELDS_BY_TYPE
@@ -133,17 +140,19 @@ class SearchText:
propertypath_set: PropertypathSet = DEFAULT_PROPERTYPATH_SET
@classmethod
- def from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family: str):
+ def from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family: str) -> frozenset[typing.Self]:
return frozenset(cls.iter_from_queryparam_family(queryparams, queryparam_family))
@classmethod
- def iter_from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family: str):
+ def iter_from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family: str) -> Generator[typing.Self]:
for (_param_name, _param_value) in queryparams.get(queryparam_family, ()):
if _param_value:
- yield cls.from_searchtext_param_or_none(_param_name, _param_value)
+ _searchtext = cls.from_searchtext_param_or_none(_param_name, _param_value)
+ if _searchtext is not None:
+ yield _searchtext
@classmethod
- def from_searchtext_param_or_none(cls, param_name: QueryparamName, param_value: str) -> SearchText | None:
+ def from_searchtext_param_or_none(cls, param_name: QueryparamName, param_value: str) -> typing.Self | None:
_propertypath_set = (
frozenset(osfmap.parse_osfmap_propertypath_set(param_name.bracketed_names[0], allow_globs=True))
if param_name.bracketed_names
@@ -161,16 +170,17 @@ def from_searchtext_param_or_none(cls, param_name: QueryparamName, param_value:
return _searchtext
@classmethod
- def queryparams_from_searchtext(self, queryparam_family: str, cardsearch_searchtext):
- _by_propertypath_set = collections.defaultdict(set)
+ def queryparams_from_searchtext(
+ self,
+ queryparam_family: str,
+ cardsearch_searchtext: Iterable[SearchText],
+ ) -> Generator[tuple[str, str]]:
for searchtext in cardsearch_searchtext:
- _by_propertypath_set[searchtext.propertypath_set].add(searchtext)
- for _propertypath_set, _combinable_segments in _by_propertypath_set.items():
_qp_name = QueryparamName(
queryparam_family,
- (osfmap.osfmap_propertypath_set_key(_propertypath_set),),
+ (osfmap.osfmap_propertypath_set_key(searchtext.propertypath_set),)
)
- yield str(_qp_name), _combinable_segments
+ yield str(_qp_name), searchtext.text
@dataclasses.dataclass(frozen=True)
@@ -186,20 +196,20 @@ class FilterOperator(enum.Enum):
AT_DATE = TROVE['at-date']
@classmethod
- def from_shortname(cls, shortname):
+ def from_shortname(cls, shortname: str) -> typing.Self:
_iri = trove_json_shorthand().expand_iri(shortname)
return cls(_iri)
def to_shortname(self) -> str:
return trove_json_shorthand().compact_iri(self.value)
- def is_date_operator(self):
+ def is_date_operator(self) -> bool:
return self in (self.BEFORE, self.AFTER, self.AT_DATE)
- def is_iri_operator(self):
+ def is_iri_operator(self) -> bool:
return self in (self.ANY_OF, self.NONE_OF)
- def is_valueless_operator(self):
+ def is_valueless_operator(self) -> bool:
return self in (self.IS_PRESENT, self.IS_ABSENT)
operator: FilterOperator
@@ -207,7 +217,7 @@ def is_valueless_operator(self):
propertypath_set: PropertypathSet = DEFAULT_PROPERTYPATH_SET
@classmethod
- def from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family: str):
+ def from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family: str) -> frozenset[typing.Self]:
return frozenset(
cls.from_filter_param(param_name, param_value)
for (param_name, param_value)
@@ -215,7 +225,7 @@ def from_queryparam_family(cls, queryparams: QueryparamDict, queryparam_family:
)
@classmethod
- def from_filter_param(cls, param_name: QueryparamName, param_value: str):
+ def from_filter_param(cls, param_name: QueryparamName, param_value: str) -> typing.Self:
_operator = None
try: # "filter[