pax_global_header00006660000000000000000000000064152466122430014517gustar00rootroot0000000000000052 comment=f1ccf5b7476910c6b23719110fb3c9cd8e25664a pallets-eco-flask-caching-93fd601/000077500000000000000000000000001524661224300167555ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/.github/000077500000000000000000000000001524661224300203155ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/.github/ISSUE_TEMPLATE/000077500000000000000000000000001524661224300225005ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000011771524661224300251160ustar00rootroot00000000000000--- name: Bug report about: Report a bug in Flask-Caching (not other projects which depend on Flask-Caching) --- Environment: - Python version: - Flask-Caching version: pallets-eco-flask-caching-93fd601/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005341524661224300244720ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Questions url: https://stackoverflow.com/search?tab=relevance&q=Flask-Caching about: Search for and ask questions about your code on Stack Overflow. - name: Questions and discussions url: https://discord.gg/pallets about: Discuss questions about your code on our Discord chat. pallets-eco-flask-caching-93fd601/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000006601524661224300261450ustar00rootroot00000000000000--- name: Feature request about: Suggest a new feature for Flask-Caching --- pallets-eco-flask-caching-93fd601/.github/dependabot.yml000066400000000000000000000002351524661224300231450ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/requirements" schedule: interval: monthly time: "08:00" open-pull-requests-limit: 99 pallets-eco-flask-caching-93fd601/.github/pull_request_template.md000066400000000000000000000017111524661224300252560ustar00rootroot00000000000000 - fixes # Checklist: - [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change. - [ ] Add or update relevant docs, in the docs folder and in code. - [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue. - [ ] Add `.. versionchanged::` entries in any relevant code docs. - [ ] Run `pre-commit` hooks and fix any issues. - [ ] Run `pytest` and `tox`, no tests failed. pallets-eco-flask-caching-93fd601/.github/workflows/000077500000000000000000000000001524661224300223525ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/.github/workflows/lock.yaml000066400000000000000000000005031524661224300241640ustar00rootroot00000000000000name: "Lock threads" on: schedule: - cron: "0 0 * * *" jobs: lock: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2 with: github-token: ${{ github.token }} issue-inactive-days: 14 pr-inactive-days: 14 pallets-eco-flask-caching-93fd601/.github/workflows/pre-commit.yaml000066400000000000000000000010331524661224300253070ustar00rootroot00000000000000name: pre-commit on: pull_request: push: branches: [main, "*.x"] jobs: main: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: 3.x - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 if: ${{ !cancelled() }} pallets-eco-flask-caching-93fd601/.github/workflows/publish.yaml000066400000000000000000000040021524661224300247000ustar00rootroot00000000000000name: Publish on: push: tags: ['*'] permissions: {} concurrency: group: publish-${{ github.event.push.ref }} cancel-in-progress: true jobs: build: runs-on: ubuntu-latest outputs: artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: false prune-cache: false - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: pyproject.toml - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV - run: uv build - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 id: upload-artifact with: name: dist path: dist/ if-no-files-found: error create-release: needs: [build] runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: artifact-ids: ${{ needs.build.outputs.artifact-id }} path: dist/ - name: create release run: gh release create --draft --repo ${GITHUB_REPOSITORY} ${GITHUB_REF_NAME} dist/* env: GH_TOKEN: ${{ github.token }} publish-pypi: needs: [build] environment: name: publish url: https://pypi.org/project/Flask-Caching/${{ github.ref_name }} runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: artifact-ids: ${{ needs.build.outputs.artifact-id }} path: dist/ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 with: packages-dir: "dist/" pallets-eco-flask-caching-93fd601/.github/workflows/tests.yaml000066400000000000000000000033301524661224300243770ustar00rootroot00000000000000name: Tests on: push: branches: - main - "*.x" paths-ignore: - "docs/**" - "*.md" - "*.rst" pull_request: paths-ignore: - "docs/**" - "*.md" - "*.rst" jobs: tests: name: ${{ matrix.name || matrix.python }} runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: include: - { python: "3.14" } - { python: "3.13" } - { python: "3.12" } - { python: "3.11" } services: memcached: image: memcached:1.6.41 ports: - 11212:11211 # health checks options: >- --health-cmd "timeout 5 bash -c 'echo > /dev/tcp/127.0.0.1/11211'" --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:8.6.3 ports: - 6360:6379 # health checks options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python }} allow-prereleases: true - name: install external dependencies run: | sudo apt-get update sudo apt-get install -y libmemcached-dev - run: uv sync --group dev --group tests - run: uv run tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} pallets-eco-flask-caching-93fd601/.github/workflows/typing.yaml000066400000000000000000000013121524661224300245450ustar00rootroot00000000000000name: Typing on: push: branches: - main - "*.x" paths-ignore: - "docs/**" - "*.md" - "*.rst" pull_request: paths-ignore: - "docs/**" - "*.md" - "*.rst" jobs: typing: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: enable-cache: true - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" allow-prereleases: true - run: uv run --no-default-groups --group tox tox run -e typing pallets-eco-flask-caching-93fd601/.gitignore000066400000000000000000000004131524661224300207430ustar00rootroot00000000000000# general things to ignore build/ dist/ *.egg-info/ *.egg *.eggs *.py[cod] __pycache__/ *.so *~ venv/ env/ .DS_Store *.swp docs/_build # due to using t/nox and pytest .tox .cache .pytest_cache .coverage htmlcov/ .xprocess .vscode .idea .venv .mypy_cache .ruff_cache pallets-eco-flask-caching-93fd601/.pre-commit-config.yaml000066400000000000000000000007431524661224300232420ustar00rootroot00000000000000ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: c59bba8fb259db0fec2bbb77ad8ba51ea7341b56 # frozen: v0.15.20 hooks: - id: ruff - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: - id: check-merge-conflict - id: debug-statements - id: fix-byte-order-marker - id: trailing-whitespace pallets-eco-flask-caching-93fd601/.readthedocs.yaml000066400000000000000000000006231524661224300222050ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-24.04 tools: python: "3.13" commands: - asdf plugin add uv - asdf install uv latest - asdf global uv latest - uv run --locked --no-default-groups --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html pallets-eco-flask-caching-93fd601/CHANGES.rst000066400000000000000000000516721524661224300205720ustar00rootroot00000000000000Changelog ========= Version 2.5.1 ------------- Released 2026-09-04 - ``@cached`` no longer treats view arguments named ``path`` or ``query_args`` as the explicit ``make_cache_key()`` arguments of the same name. :issue:`679` Version 2.5.0 ------------- Released 2026-08-24 - Specifying timeouts now also works with ``datetime.timedelta``. :issue:`266` - Add config option ``CACHE_SERIALIZER`` to set the cachelib serializer the backend uses. :issue:`209` - cachelib backends can now be configured directly via ``CACHE_TYPE="cachelib.ValkeyCache``. :pr:`668` - ``@cached`` and ``@memoize`` now go through the public ``Cache`` proxy methods (``get``, ``set``, ``has``, ``delete``, ``delete_many``, ``get_many`` and ``set_many``) instead of calling the backend directly. :issue:`417` - Document that ``CACHE_REDIS_HOST`` accepts an already created ``redis.Redis`` client instead of a host name, which allows sharing a connection pool with other extensions and the application. :issue:`629` - Send Signals for cache hits and misses. :pr:`#237` and :pr:`667` - Include ``key_prefix`` when building ``@cached(query_string=True)`` cache keys. :issue:`302` - Add ``Cache.delete_cached()`` and extend ``make_cache_key()`` with ``path`` and ``query_args`` arguments to make deleting views decorated with ``cached(query_string=True)`` possible. :issue:`243` - Use ``hashlib.sha256`` instead of ``hashlib.md5`` for hashing the cache keys. This changes the generated keys, so entries cached by an earlier version become obsolete. If you wish to still use hashlib.md5 set the config ``CACHE_HASH_METHOD = hashlib.md5``. :pr:`563` - Add ``CACHE_HASH_METHOD`` to set the hash method used for the cache keys of ``@cached`` and ``@memoize``. - Drop support for Python 3.10 and require cachelib 0.17.0+ - ``CACHE_IGNORE_ERRORS`` is now used by every backend instead of only ``SimpleCache`` and ``FileSystemCache``. - The ``delete_many`` method is now used from cachelib. A key that does not exist counts as deleted, and with ``CACHE_IGNORE_ERRORS`` set to ``False`` a ``RuntimeError`` is raised if the key could not be deleted. - Clarify docs about decorator order regarding ``@staticmethod`` and ``@classmethod`` when memoizing. :issue:`440` - Remove the deprecated lowercase ``CACHE_TYPE`` names (``null``, ``simple``, ``filesystem``, ``redis``, ``redissentinel``, ``rediscluster``, ``uwsgi``, ``memcached``, ``gaememcached``, ``saslmemcached`` and ``spreadsaslmemcached``) - Call ``@memoize`` ``forced_update`` callbacks once per decorated function call instead of once while making the key and again before cache lookup. :issue:`387` - Add an ``is_stale`` option to ``@cached`` and ``@memoize``. It is called on a cache hit with the cached value. Unlike ``forced_update`` it uses the value itself to check whether the cache is stale. Can be used in combination with ``forced_update`` :issue:`392` - ``@cached`` now caches an ``HTTPException`` raised by the view (i.e. through ``abort()``). This exception will now be re-raises on a cache hit. Use ``response_filter``, to keep the exception out of the cache. :issue:`444` - Fix a ``@memoize`` cache-key collision when a parameter has a falsy default (e.g. ``0``, ``""``, ``False``): calling with the default was keyed the same as passing ``None``, returning the wrong cached result. :pr:`656` - ``@cached`` and ``@memoize`` now preserve the wrapped function's signature and expose ``uncached``, ``cache_timeout``, ``make_cache_key`` ``delete_memoized`` to type checkers. - Fix ``@cached(response_hit_indication=True)`` appending a new ``after_request`` function to the app on every request. - Fix a view returning an iterator of strings, for example when using ``flask.stream_template`` it previously returned a list of JSON strings instead of the rendered template. :issue:`511` - Fix ``__caching_id__`` never being usable. ``@memoize`` silently ignored that and ran the function uncached. - Add ``CACHE_FILE_HASH_METHOD`` config option to ``FileSystemCache`` to allow using a different hash function for cache keys. :pr:`660` - Add ``pool_size`` and ``pool_blocking`` to make ``MemcachedCache`` backends thread-safe. :pr:`663` - Modify ``SimpleCache`` docs as it's now thread-safe using an ``RLock`` for all operations. :pr:`663` - ``delete_memoized`` now takes the instance from a bound method, so ``cache.delete_memoized(obj.method, 1)`` deletes the cache for ``obj.method(1)`` instead of silently deleting nothing. The current behaviour by passing the instance keeps working as well. :issue:`554` - The ``{% cache %}`` Jinja tag no longer propagates backend errors. If the cache is unavailable, the block is rendered normally instead of raising, matching the existing behavior of ``@memoize``. :issue:`564` :pr:`565` - Fix ``@memoize`` invalidating a function's entire cache by not updating the memoize version key's timeout. It is now refreshed each time an entry is written, with that entry's own timeout. :issue:`531` Version 2.4.1 ------------- Released 2026-07-08 - Use ``pallets_sphinx_themes`` for the documentation. - Type ``CachedResponse.__init__`` so it doesn't trigger mypy ``[no-untyped-call]`` in strict downstream codebases. :issue:`628` - Docs: clarify that ``@memoize`` uses ``repr(obj)`` (or ``__caching_id__``) for the ``self``/``cls`` identity, not Python's :func:`id`. :issue:`555` - Fix test failure with pytest 9.1. :issue:`651` Version 2.4.0 ------------- Released 2026-04-17 - Modernize project setup - Update ``intersphinx_mapping`` for Sphinx 8 compatibility. :pr:`599`, :issue:`598` - Pass CACHE_OPTIONS as kwargs to redis_from_url. :pr:`591` - Pass sentinel_kwargs to redis client via CACHE_OPTIONS :pr:`626` - Fix ``response_hit_indication`` return True always. :pr:`579`, :pr:`596`, :issue:`595`, :issue:`570` - Use ``FORWARDREF`` annotation format when introspecting decorated functions so that ``@memoize`` / ``@cached`` work with ``TYPE_CHECKING``-only annotations on Python 3.14. :issue:`636` Version 2.3.1 ------------- Released 2025-02-22 - Relax cachelib version to allow latest releases Version 2.3.0 ------------- Released 2024-05-04 - Added ``response_hit_indication`` flag to ``Cache.cached`` decorator for appending 'hit_cache' headers to responses, indicating cache hits. Version 2.2.0 ------------- - Drop python 3.7 support - python 3.11 officially supported - Fix issue causing `args_to_ignore` to not work with `flask_caching.Cache.memoize` decorator when keyword arguments were used in the decorated function call Version 2.1.0 ------------- Released 2024-10-08 - fix type signature in ``flask_caching.utils.make_template_fragment_key``. :pr:`430` - Added docs and example for make_cache_key - support Flask 3 Version 2.0.2 ------------- Released 2023-01-12 - fix issue with boto3 dependencie due to latest cachelib released - migrate ``flask_caching.backends.RedisCluster`` dependency from redis-py-cluster to redis-py - bug fix: make the ``make_cache_key`` attributed of decorated view functions writeable. :pr:`431`, :issue:`97` Version 2.0.1 ------------- Released 2022-07-30 - Relax dependency pin to allow Flask 2.x.x Version 2.0.0 ------------- Released 2022-06-26 - fix bug where ``flask_caching.backends.RedisSentinelCache.get_many`` would query wrong host&port combination. :pr:`372` - Remove ``flask_caching.backends.FileSystemCache`` method overrides. It now shares 100% of ``cachelib.FileSystemCache`` API and is fully compatible. Functionality relient on implementation details of said overrides from older releases might not work anymore. :pr:`369` - Add proxy to underlaying ``has`` method of cache clients. :pr:`356` - ``flask_caching.backends.FileSystemCache`` now stores timestamps in a universal (non-frammed) way following the lastest version of ``cachelib.FileSystemCache``. The change also reduces overhead from 17 bytes (via previous method using pickle) to 4 bytes (using python's ``struct``). This, however, will break compatibily since older timestamps are serialized with a different strategy. Version 1.11.1 -------------- Released 2022-05-27 - Add cachelib to setup.py: :pr:`354` Version 1.11.0 -------------- Released 2022-05-27 - Add suport for cached/memoized generators. :pr:`286` - Add support for Flask 2.0 async. :pr:`282` - Cachelib is now used as backend. :pr:`308` - Drop support for python 3.6. :pr:`332` - Add support for dynamic cache timeouts `#296` - Fix bug in ``CACHE_OPTIONS`` reading for redis in ``RedisSentinelCache``. :pr:`343` Version 1.10.1 -------------- Released 2021-03-17 - A ``GoogleCloudStorageCache`` backend has been added to the user contributed caching backends. :pr:`214` - Fix a regression introduced in the last release which broke all applications subclassing the ``Cache`` class. - Add test_generic_get_bytes test case. :pr:`236` - Various improvements and fixes. Version 1.10.0 -------------- Released 2021-03-04 - **Important**: The way caching backends are loaded have been refactored. Instead of passing the name of the initialization function one can now use the full path to the caching backend class. For example: ``CACHE_TYPE="flask_caching.backends.SimpleCache"``. In the next major release (2.0), this will be the only supported way. - UWSGICache is not officially supported anymore and moved to the user contributed backends. - Switch from Travis-CI to GitHub Actions - Fix add() in RedisCache without a timeout. :pr:`218` - Fix error in how the FileSystemCache counts the number of files. :pr:`210` - Type Annotations have been added. :pr:`198` - Add some basic logging to SimpleCache and FileSystemCache for better observability. :pr:`203` - Add option in memoize to ignore args :pr:`201` - Stop marking wheels as Python 2 compatible. :pr:`196` - Fix ``default_timeout`` not being properly passed to its super constructor. :pr:`187` - Fix ``kwargs`` not being passed on in function ``_memoize_make_cache_key``. :pr:`184` - Add a Redis Cluster Mode caching backend. :pr:`173` - Do not let PIP install this package on unsupported Python Versions. :pr:`179` - Fix uWSGI initialization by checking if uWSGI has the 'cache2' option enabled. :pr:`176` - Documentation updates and fixes. Version 1.9.0 ------------- Released 2020-06-02 - Add an option to include the functions source code when generating the cache key. :pr:`156` - Add an feature that allows one to completely control the way how cache keys are generated. For example, one can now implement a function that generates a cache key the based on POST requests. :pr:`159` - Fix the cache backend naming collisions by renaming them from ``simple`` to ``simplecache``, ``null`` to ``nullcache`` and ``filesystem`` to ``filesystemcache``. - Explicitly pass the ``default_timeout`` to ``RedisCache`` from ``RedisSentinelCache``. - Use ``os.replace`` instead of werkzeug's ``rename`` due to Windows raising an ``OSError`` if the dst file already exist. - Documentation updates and fixes. Version 1.8.0 ------------- Released 2019-11-24 - **BREAKING:** Removed support for Python 2. Python 3.5 and upwards are supported as of now. - Add option to specify if ``None`` is a cached value or not. See :pr:`140` and `#141` - Allow to use ``__caching_id__`` rather than ``__repr__`` as an object caching key. :pr:`123` - The RedisCache backend now support generating the key_prefix via a callable. :pr:`109` - Emit a warning if the ``CACHE_TYPE`` is set to ``filesystem`` but no ``CACHE_DIR`` is set. - Fixes Google App Engine Memcache backend. See issue `#120` for more details. - Various documentation updates and fixes. Version 1.7.2 ------------- Released 2019-05-28 **This is the last version supporting Python 2!** - Do not run a cached/memoized function if the cached return value is None. :pr:`108` Version 1.7.1 ------------- Released 2019-04-16 - Fix introspecting Python 3 functions by using varkw. :pr:`101` - Remove leftover files (``uwsgi.py``) in PyPI package. See issue `#102` for more details. Version 1.7.0 ------------- Released 2019-03-29 - Added a feature called 'response_filter' which enables one to only cache views depending on the response code. :pr:`99` - A DeprecationWarning got turned into a TypeError. Version 1.6.0 ------------- Released 2019-03-06 - The ``delete_many`` function is now able to ignore any errors and continue deleting the cache. However, in order to preserve backwards compatibility, the default mode is to abort the deletion process. In order to use the new deletion mode, one has to flip the config setting ``CACHE_IGNORE_ERRORS`` to ``True``. This was and still is only relevant for the **filesystem** and **simple** cache backends. :pr:`94` - Re-added the ``gaememcached`` CACHE_TYPE for improved backwards compatibility. - Documentation improvements Version 1.5.0 ------------- Released 2019-02-23 - Add support for a Redis Sentinel Cluster. :pr:`90` - Parameterize the hash function so alternatives can be used. :pr:`77` - Include the deprecated ``werkzeug.contrib.cache`` module in Flask-Caching. :pr:`75` Version 1.4.0 ------------- Released 2018-04-16 - Fix logic for creating key for var args in memoize. :pr:`70` - Allow older Werkzeug versions by making the UWSGICache backend conditional. :pr:`55` - Some documentation improvements. :pr:`48`, `#51`, `#56`, `#67` - Some CI improvements. :pr:`49`, `#50` Version 1.3.3 ------------- Released 2017-06-25 - Add support for multiple query params and use md5 for consistent hashing. :pr:`43` Version 1.3.2 ------------- Released 2017-06-25 - Fix ``spreadsaslmemcached`` backend when using Python 3. - Fix kwargs order when memoizing a function using Python 3.6 or greater. See `#27` Version 1.3.1 ------------- Released 2017-06-20 - Avoid breakage for environments with Werkzeug<0.12 installed because the uwsgi backend depends on Werkzeug >=0.12. See `#38` Version 1.3.0 ------------- Released 2017-06-17 - Add uWSGI Caching backend (requires Werkzeug >= 0.12) - Provide a keyword `query_string` to the cached decorator in order to create the same cache key for different query string requests, so long as they have the same key/value (order does not matter). :pr:`35` - Use pytest as test suite and test runner. Additionally, the tests have been split up into multiple files instead of having one big file. Version 1.2.0 ------------- Released 2017-02-02 - Allows functions with kwargs to be memoized correctly. See `#18` Version 1.1.1 ------------- Released 2016-12-09 - Fix PyPI Package distribution. See `#15` Version 1.1.0 ------------- Released 2016-12-09 - Fix 'redis' backend import mechanisim. See `#14` - Made backends a module to better control which cache backends to expose and moved our custom clients into a own module inside of the backends module. See also `#14` (and partly some own changes). - Some docs and test changes. See `#8` and `#12` Version 1.0.1 ------------- Released 2016-08-30 - The caching wrappers like `add`, `set`, etc are now returning the wrapped result as someone would expect. See `#5` Version 1.0.0 ------------- Released 2016-07-05 - Changed the way of importing Flask-Cache. Instead of using the depreacted method for importing Flask Extensions (via ``flask.ext.cache``), the name of the extension, ``flask_cache`` is used. Have a look at `Flask's documentation` for more information regarding this matter. This also fixes the deprecation warning from Flask. - Lots of PEP8 and Documentation fixes. - Renamed this fork Flask-Caching (``flask_caching``) as it will now be available on PyPI for download. In addition to the above mentioned fixes, following pull requests have been merged into this fork of `Flask-Cache`: - `#90 Update documentation: route decorator before cache` - `#95 Pass the memoize parameters into unless().` - `#109 wrapped function called twice` - `#117 Moves setting the app attribute to the _set_cache method` - `#121 fix doc for delete_memoized` - `#122 Added proxy for werkzeug get_dict` - `#123 "forced_update" option to 'cache' and 'memoize' decorators` - `#124 Fix handling utf8 key args` (cherry-picked) - `#125 Fix unittest failing for redis unittest` - `#127 Improve doc for using @cached on view` - `#128 Doc for delete_memoized` - `#129 tries replacing inspect.getargspec with either signature or getfullargspec if possible` - `make_cache_key() returning incorrect key` (cherry-picked) Version 0.13 ------------ Released 2014-04-21 - Port to Python >= 3.3 (requiring Python 2.6/2.7 for 2.x). - Fixed bug with using per-memoize timeouts greater than the default timeout - Added better support for per-instance memoization. - Various bug fixes Version 0.12 ------------ Released 2013-04-29 - Changes jinja2 cache templates to use stable predictable keys. Previously the key for a cache tag included the line number of the template, which made it difficult to predict what the key would be outside of the application. - Adds config variable `CACHE_NO_NULL_WARNING` to silence warning messages when using 'null' cache as part of testing. - Adds passthrough to clear entire cache backend. Version 0.11.1 -------------- Released 2013-04-7 - Bugfix for using memoize on instance methods. The previous key was id(self), the new key is repr(self) Version 0.11 ------------ Released 2013-03-23 - Fail gracefully in production if cache backend raises an exception. - Support for redis DB number - Jinja2 templatetag cache now concats all args together into a single key instead of treating each arg as a separate key name. - Added delete memcache version hash function - Support for multiple cache objects on a single app again. - Added SpreadSASLMemcached, if a value is greater than the memcached threshold which defaults to 1MB, this splits the value across multiple keys. - Added support to use URL to connect to redis. Version 0.10.1 -------------- Released 2013-01-13 - Added warning message when using cache type of 'null' - Changed imports to relative instead of absolute for AppEngine compatibility Version 0.10.0 -------------- Released 2013-01-05 - Added `saslmemcached` backend to support Memcached behind SASL authentication. - Fixes a bug with memoize when the number of args != number of kwargs Version 0.9.2 ------------- Released 2012-11-18 - Bugfix with default kwargs Version 0.9.1 ------------- Released 2012-11-16 - Fixes broken memoized on functions that use default kwargs Version 0.9.0 ------------- Released 2012-10-14 - Fixes memoization to work on methods. Version 0.8.0 ------------- Released 2012-09-30 - Migrated to the new flask extension naming convention of flask_cache instead of flaskext.cache - Removed unnecessary dependencies in setup.py file. - Documentation updates Version 0.7.0 ------------- Released 2012-08-25 - Allows multiple cache objects to be instantiated with different configuration values. Version 0.6.0 ------------- Released 2012-08-12 - Memoization is now safer for multiple applications using the same backing store. - Removed the explicit set of NullCache if the Flask app is set testing=True - Swapped Conditional order for key_prefix Version 0.5.0 ------------- Released 2012-02-03 - Deleting memoized functions now properly functions in production environments where multiple instances of the application are running. - get_memoized_names and get_memoized_keys have been removed. - Added ``make_name`` to memoize, make_name is an optional callable that can be passed to memoize to modify the cache_key that gets generated. - Added ``unless`` to memoize, this is the same as the unless parameter in ``cached`` - memoization now converts all kwargs to positional arguments, this is so that when a function is called multiple ways, it would evaluate to the same cache_key Version 0.4.0 ------------- Released 2011-12-11 - Added attributes for uncached, make_cache_key, cache_timeout to the decorated functions. Version 0.3.4 ------------- Released 2011-09-10 - UTF-8 encoding of cache key - key_prefix argument of the cached decorator now supports callables. Version 0.3.3 ------------- Released 2011-06-03 Uses base64 for memoize caching. This fixes rare issues where the cache_key was either a tuple or larger than the caching backend would be able to support. Adds support for deleting memoized caches optionally based on function parameters. Python 2.5 compatibility, plus bugfix with string.format. Added the ability to retrieve memoized function names or cache keys. Version 0.3.2 ------------- Bugfix release. Fixes a bug that would cause an exception if no ``CACHE_TYPE`` was supplied. Version 0.3.1 ------------- Pypi egg fix. Version 0.3 ----------- - CACHE_TYPE changed. Now one of ['null', 'simple', 'memcached', 'gaememcached', 'filesystem'], or an import string to a function that will instantiate a cache object. This allows Flask-Cache to be much more extensible and configurable. Version 0.2 ----------- - CACHE_TYPE now uses an import_string. - Added CACHE_OPTIONS and CACHE_ARGS configuration values. - Added delete_memoized Version 0.1 ----------- - Initial public release pallets-eco-flask-caching-93fd601/CONTRIBUTING.md000066400000000000000000000136151524661224300212140ustar00rootroot00000000000000# How to contribute to Flask-Caching Thank you for considering contributing to Flask-Caching! ## Support questions Please don't use the issue tracker for this. The issue tracker is a tool to address bugs and feature requests in Flask-Caching itself. Use one of the following resources for questions about using Flask-Caching or issues with your own code: - The `#get-help` channel on our Discord chat: - Ask on [Stack Overflow](https://stackoverflow.com/search?tab=relevance&q=Flask-Caching). Search with Google first using: `site:stackoverflow.com Flask-Caching {search term, exception message, etc.}` ## Reporting issues Include the following information in your post: - Describe what you expected to happen. - If possible, include a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) to help us identify the issue. This also helps check that the issue is not with your own code. - Describe what actually happened. Include the full traceback if there was an exception. - List your Python and Flask-Caching versions. If possible, check if this issue is already fixed in the latest releases or the latest code in the repository. ## Submitting patches If there is not an open issue for what you want to submit, prefer opening one for discussion before working on a PR. You can work on any issue that doesn't have an open PR linked to it or a maintainer assigned to it. These show up in the sidebar. No need to ask if you can work on an issue that interests you. Include the following in your patch: - Use [Black](https://black.readthedocs.io) to format your code. This and other tools will run automatically if you install [pre-commit](https://pre-commit.com) using the instructions below. - Include tests if your patch adds or changes code. Make sure the test fails without your patch. - Update any relevant docs pages and docstrings. Docs pages and docstrings should be wrapped at 72 characters. - Add an entry in `CHANGES.rst`. Use the same style as other entries. Also include `.. versionchanged::` inline changelogs in relevant docstrings. ### First time setup - Download and install the [latest version of git](https://git-scm.com/downloads). - Configure git with your [username](https://docs.github.com/en/github/using-git/setting-your-username-in-git) and [email](https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address). ```bash $ git config --global user.name 'your name' $ git config --global user.email 'your email' ``` - Make sure you have a [GitHub account](https://github.com/join). - Fork Flask-Caching to your GitHub account by clicking the [Fork](https://github.com/pallets-eco/flask-caching/fork) button. - [Clone](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork) the main repository locally. ```bash $ git clone https://github.com/pallets-eco/flask-caching $ cd flask-caching ``` - Add your fork as a remote to push your work to. Replace `{username}` with your username. This names the remote "fork", the default Pallets remote is "origin". ```bash git remote add fork https://github.com/{username}/flask-caching ``` - Create a virtualenv. **Linux/macOS** ```bash $ python3 -m venv env $ . env/bin/activate ``` **Windows** ```bash > py -3 -m venv env > env\Scripts\activate ``` - Upgrade pip and setuptools. ```bash $ python -m pip install --upgrade pip setuptools ``` - Install the development dependencies, then install Flask-Caching in editable mode. ```bash $ pip install -r requirements/dev.txt && pip install -e . ``` - Install the pre-commit hooks. ```bash $ pre-commit install ``` ### Start coding - Create a branch to identify the issue you would like to work on. If you're submitting a bug or documentation fix, branch off of the latest ".x" branch. ```bash $ git fetch origin $ git checkout -b your-branch-name origin/main ``` If you're submitting a feature addition or change, branch off of the "main" branch. ```bash $ git fetch origin $ git checkout -b your-branch-name origin/main ``` - Using your favorite editor, make your changes, [committing as you go](https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes). - Include tests that cover any code changes you make. Make sure the test fails without your patch. Run the tests as described below. - Push your commits to your fork on GitHub and [create a pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). Link to the issue being addressed with `fixes #123` in the pull request. ```bash $ git push --set-upstream fork your-branch-name ``` ### Running the tests Run the basic test suite with pytest. ```bash $ pytest ``` This runs the tests for the current environment, which is usually sufficient. CI will run the full suite when you submit your pull request. You can run the full test suite with tox if you don't want to wait. ```bash $ tox ``` ### Running test coverage Generating a report of lines that do not have test coverage can indicate where to start contributing. Run `pytest` using `coverage` and generate a report. ```bash $ pip install coverage $ coverage run -m pytest $ coverage html ``` Open `htmlcov/index.html` in your browser to explore the report. Read more about [coverage](https://coverage.readthedocs.io). ### Building the docs Build the docs in the `docs` directory using Sphinx. ```bash $ cd docs $ make html ``` Open `_build/html/index.html` in your browser to view the docs. Read more about [Sphinx](https://www.sphinx-doc.org/en/stable/). pallets-eco-flask-caching-93fd601/LICENSE000066400000000000000000000060571524661224300177720ustar00rootroot00000000000000Copyright (c) 2010 by Thadeus Burgess. Copyright (c) 2016 by Peter Justin. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The "cache" module from werkzeug is licensed under a BSD-3 Clause license as is stated below: Copyright (c) 2017, Pallets Team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pallets-eco-flask-caching-93fd601/README.md000066400000000000000000000021131524661224300202310ustar00rootroot00000000000000# Flask-Caching A fork of the [Flask-Cache](https://github.com/thadeusb/flask-cache) extension which adds easy cache support to Flask. ## Pallets Community Ecosystem > [!IMPORTANT]\ > This project is part of the Pallets Community Ecosystem. Pallets is the open > source organization that maintains Flask; Pallets-Eco enables community > maintenance of related projects. If you are interested in helping maintain > this project, please reach out on [the Pallets Discord server][discord]. [discord]: https://discord.gg/pallets ## Installing You can install the extension with the following commands: ```text $ pip install -U Flask-Caching ``` or alternatively with uv: ```text $ uv add Flask-Caching ``` Flask-Caching 2.5.0 supports Python 3.11+. ## Links - Documentation: - Changes: - PyPI Releases: - Source Code: - Issue Tracker: pallets-eco-flask-caching-93fd601/docs/000077500000000000000000000000001524661224300177055ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/docs/Makefile000066400000000000000000000167221524661224300213550ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-Caching.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-Caching.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-Caching" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-Caching" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." pallets-eco-flask-caching-93fd601/docs/_static/000077500000000000000000000000001524661224300213335ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/docs/_static/flask-cache.png000066400000000000000000000351051524661224300242060ustar00rootroot00000000000000‰PNG  IHDRb5 +vsRGB®ÎébKGDÿÿÿ ½§“ pHYsa(™atIMEÚ/+Otn IDATxÚí�y|TÕÝÿßß™L& a_EdTÜm«uM¢‰Zµ­­Ö.j«õÑêÓ½µZµÖGÛŸZ­¶Úª�ö©ÕºL4µî¸á†¸cD…„$$3™Ì|œ3äfr'™, çór sgî2çÜ{¾ŸïÝAÜ <ÊÊ+†ƒ„UuaM´j¥‡�DÀ Á“ƒ²ÊÃTyQѨˆ¼^ZVñ}72‰ ‚$å•STxXD& "‹È±Ó¦Ï¬¯­]¼À�’ƒƒƒƒƒ³ >|[” €Ú÷âù÷ïËÊ+�%ÁÁÁÁÁÁ„AeŽÏVCT €ËKË+OtåàààààÂ`âÂò¤ÀKDÕQ7”•UìçFËÁÁÁÁÁ„A�;­Ïú±ˆã¹»´¬b®1GTu‘"?bŠŠ?‡@E˜""w–•Uîæý°¬¬ÒÍŸƒƒƒƒÃæRb¥å•y¨þF�_ ˆÏœ(í.ˆgUõ;ˆ/èWAŠTõE¹2ZùÐ�¦ƒƒƒƒƒ#ÛI¨An¾“A ð! ëUu”IŠÜ„@e´:òFoÎ_VVYˆP¬Ê0„±(cE ˜ É°‘Ï]+ÈgÀT›£Ñª˜›EG6ÊÊ* Uä&�oeùŠ¢ªvÖDDAôUΨ‰VµäDJÊ*F‹ÈÀ¾Šî)È4UvD)P°é¤ " ¨5pÐ ¬>ƒ.RåUÞŠVWmp³éàààà‚C¿’„ÊQ·ÇwaEÈÆÞÕ²h´ê£.ÏQ^¹pp„ÂŽ‚ÉS5À°�ì§nÿÄþK‚4ªêrAQô5ѪwÝŒ:8888‚àÐ_$¡¼r"ðÊ> ˆä@Ì7ÞQ(¯©Ž|äC<ò=XD~ …jã¢Ð~º4ã~Š¡ú"}#Z]•t3ëàààà‚CQZ^9[à`·œ„³Qþÿ|;Ziñ��0p0p®µJ:úí®¤U„»�›}¥ÆGúlIøè ¡n…1º8©¦ºê Ïþ3€Ÿ #èC°¹‘iQXÜ�rU4ùÔÍ®ƒƒƒÃÖ—G¿õâ[ªtO”·AŽÍ '+üÕÓ! ¢*5úPµ¿®Ý¦kn:â8EÏ}¦´¼ò(7µÛöþ,6Ø5©Xí†ÇY6�õà\U½A@ÉÈgÜDT‘çUùzM4²ÒìWQ¢È¯~â%ÙÈ€ˆ¨ªúÕ]h‘$�ôX@@UóD$èÝÏ^¢ªªø_.ž4mù•ªþ¹&ZµÑͶƒÃ6‡©–”fù|pÐæ†Ê‡~DiYÅ‹€OgÎÇÄ,(+«˜‚È�Ê®ÈAš°)ND$ …V…Ã+‹ŠŠV )²º¸¸x]QQaS^(”�xkk¨¹¹¥¤ycÓ¨¦�MZZZ&Æb±�ÛÚÚ&¤R©<ÙDº¸¯ìµ«‚ܪª¿¨‰V}îfÝÁa›Á·�€¢,Ÿ·s�ÜP9‚àп–ƒ!À½À1YÉ�ª ¼œPS]µÜZ¦ÿr@ZwAPUT•p8¼bô¨ÑOLÝuÚK³gïöÑ®»î²aü[‹‹‹“¡*&‹Ñ’sEmmHý†Æ¼O?]‘ÿAí²áï¼óî”eË–¼~ýº/'‰q–(d·&¤mæBŸ¾­Ž¬v³ïà°U#\tׂþ\àÏn¸ApèoëAyå€?ûÉ÷´…^a…ÀáÑêÈK*vj€™íÆÍÝlUU-,™ºë®wžö�ÓŸÜcÏ�±X*ÐÚÚH¥RYï‡`0 �� Á–––àøñ;Ä[[S´  œ ‡Ñùó_yϽw—®X±òÔD"¾“H`Ó9É^R@Ÿùj´:ò‰»¶J î¾ $€«€Û�åÀ³ö°70¸P7dŽ 8ô¯õ` ÊKcÈæ€:�oF«#QK(&ƒÞ+Ê>dÄžÀC 燗Mœ¸ó½çœsNdæÌ) --©üX¬5�¦�5~UUdèЂ¶Ûo¿sZÍ#5?I$c¦NÝõöÿ¹êÊ7nl ¦IEQQA2?Ÿø‚ ÇÜvûm'úé''$‰�ìq³YÌ5+÷+z¶s788luØ x˜mŸ×ã€j7,ƒ.‹aëÁåÙÈ�šm)A¯Uc- ´¼b„Àu‚ì£t&–(@ ˆí°Ãw~ó›ßü¯ë¯¿æ®©S§´Ô×Ç âñÖ@ ê‰È H0˜§�<úØ£ç566î�Ç'ðÁâ Ÿ|jþè’’üMu š›cÁúúXÑ>ûìÕpóM×ßr|åñç�9*‚Çzщ ÚÒÑ'"\YZVá«ÃæÆHàTà/˜`ºµ˜Èû$ð9ð<ð;`W7TL±ã1Û¾ÿ‹#ƒ yn¥eGY>N÷Zx•?ÖD#j$«üR ›G¤39M¥R’—ª›»çž—�þùÏImØÏOg XRÐ¥P.)ÉKÜpã_öhjj:$¤�@SÒÉò ªMM± ˆ¾öµS—•••_vÍ5×¼úÞûïþ,•Jû¹LÄ¢ *ßÑ�1&J‡þV„Ž~`ÿf[÷FóìëÇÀåÀoœ&ó±ÀãÀÎö}p…»•œÁa ¢¬¼²@�3QMGKG%AÙr~4i²ûœŒp‘ˆ¦3-¨ª„ÃK+�«8û²Ë.~4*H% 1ò\ÈšŽè½9xùå—OH¥RÁ´ xøá_ú¬©©-ØÑ ›Üñxk .L]}õrÈ!? …B«,1ÉXlE±‘òÓ²òÊRwW8ô8xËj¾Çö@)Ê.~=HÇí`²gÛC˜ælŽ 8lAÌ=,‹Ä¶rS¯ŒVGZr0Cá:µv/¡ð¤0RXX´ð”“O¾ð{g~뽺º–OÀ`NPU-,ÌOýû¾ÈÎõõ6Y†¾0 µ­­M²hW›‚ëëãù?ûéE/~鋇þ2??yš$tv7¨…À%eå•;ºÛ¡ïÏÏÿÂÑ=üØËjÇùÀ˜ä�vqœ‹�éƒl쎳–/îv·”#[Öz ªz"cüd´Ôo¨òWûý0p© ãýRÓn…p¸`ññÇé)§œüQ}},_ÄÄг TQ…gŸ�H[[Û(<…�&NœøL3�lû‹Í�lhh ]xá_?úèc~ …>I¥R>1 éjKì¯è™Û¨Ö5qݾ#­ ne×.^ÅTú»Øã»xø �¿ˆ` þüW–ãé>µo{ÃÏ2Þ'0�ŠŽ 8l1(£Eä/!è(p4!"‘ttÿ ǤC|”~ÉËË«++-ýõißüê²�ãA ‹ò†]\ðî»oY½úÓ/�†Òç ‚ {î±×’ÖÖÜLµiÒR_ ýàûß}÷K_:ôçyyy|-6IS�óJË*flCÄà{ÀËöuîv~××Ùßz/ÆW¿µ`ðð+àE`ð#`e÷ûŸ€›³|^>ˆV¥™ÀA۞܂í‚Ö-媛´NŸ’ÇR TG«#ZV^Q úQ†eÆx´ûø^{í}é™gžñv]]K¨ëÚÇ]¬–ª’ŸJ=õôü©ÍÍÍ»{IC8œÿá^{Ï]×ÒbÈGnÁOª«k �÷ÃsMž<ù&iÅSͱ}T�‘"rñ60ƒÇb"áÿì‹1Yº�Þ­…V³|8Sn7Œ1åo 8x ظ8 XÜÃc\ IJ�üA²*}ÅgÛÜb=8á²ß¶a™î‚t]¤H´:²ÂJÏC€cüœ鬄ñãÇÿãç?ûù3õõñ+”{C�¢¢`òÍ7™L&K�À¦s” )yoÌèá­­­zt| DãñdàÜsÏ‹\rÉÅûnذáˆÎ™ ›ŽyriYåÍ5ÑÈünŽ{Ƭ¼c:®§=m­ÍþMÚs-)Nÿ Øg ØóŒ6Z�³Heœó<{Þ]�¡Ÿm�õç¯Á”ðžâ£Tl ©©ßþnçõ;Àm½<ÎgÀÃÀIžmõÀ öž (óÙö¤[ªApØ‚(-«˜­Ê�vyÍ\dEÑ&Tîôl»Õ f¸ ÒxAAÁû•÷´µ%Å~E{¹xKqqAòÉ'ŸóùçëŽîØ_Atä¨ÑïC>"½‘ƒ"mmm̘¾ËÆcŽ)½ñÞ{ïÝ/•Jó+ ¤JH„s1='²a<&g=Ý·¢Ù¾Úì{ïËv˜$àùë% ùV#�Åä3LÓ™ZÏ9 \dI„bÛÙ­úwà«d¯½?Ðáí›{„‚ä’ÚC?^¹òÄ4IS’¼¼¼uS§NYí­ÒØ b$ñ8ß?ëûÿ‡ÃK={‡‘Pt$pD7‡<øo`I–Ï×`⳯ÿXMñiûÙÛ–làC2Ç1Qïó1®‹L¤¶³Ûõ?˜øŠë0•;¾º®ïÿÏþûZ²öbžÏ5ƒliú’϶gÝŠí‚Ã…îŒê,ÿ�X-ð@iyÅXà(�VëÕöEU™6mÚ]3gNÝÐÒësÊY8LÛíwÜvH¼5>Ùv~”t}…p¸àãY³fÖÛ÷}²"´´Ä{Î�Õ¸óÎ;ßkO'7‹ !àð²òÊ’®Že…öIVÓÍÄëV<8Ú¾ŽŽÄ²ìŽÉ�¿ã¢H£9Ë9_Ç4®yÕç³ävxÃ6`²ÛJÂQ`R�—€Ÿöã±?·÷Ró \˜¾à³m>Ž 8lAz LE®�×ô¦ç£Õ‘u†ÈþÀ8Íøªˆ�J¥‡ÃË¿û�3ŸŠÅ§­ ½¾]³º®`éÒ¥¥öÞØÔ§AUSC† Y2eòä>ûØÓ䢡¡58oÞÁχB¡®ÒÐæ‚î�Ãaá߃>×à²5˜î×sÜ· x`�„4¶†`µ]�{0¦ðfL_…þ Ô~œCÁ¸¼¦3ìß ˜Œ�­ …À>ÛbW‹Ã … RÜ (+¯¡2ÑQ qTb …¸ÀÐ"|‚ms‚g=«ÕA¢„3ö´ß~ìØq�LŸ>©¡®.VÐÛÀDT•¼¼�>¹êÆæ�{�™UÉÆ�{?˜GŠ~(Žc ŽÌ;h¿µ=YTWW7Ñ�I!2e.³a]?LßO0U÷Fñn¾û*Æ¥$a!&ÆÂ+ඤK¥g0̾ÿ�cD‚±Rýc¡ÊfýÚ`¯ý}Kn_Ÿ¼zëïŸdÇãL&ÎŒ[èæ­<ûb²�¼x1‡g M.N¾‹±ÄÝœÕc9S¤ê(L±+ÅL^†mZçà¶F†‚\ œ2Æh7ÒÔ«j=P‡ÈŒùÜ1Þ(-«"舳hü±9³ç<ŸH²q‰½5û+ ÉdL-zó`[9qq ƒñY³f}@fí /Þ† vŠ�·ÃkuuuGb2 Äsàô߃É-­©¦ðEÚëö¿ÞÍw?·ZVÑ Ì-�U@ãjÀ—Ñ^wá-Ú·Ln Ý%؆Ùß2øšäOôr ÿ¦É™w^fÆdÛü.‡ãÌóÙöL7û„-ø&ð7�ƒú8Ž€«0é«™8È>ŸåŽ$8‚° Bþ|Ï[Ú@…‘“¼‚ÔOÕ·ÛÖ(ºÂ ÞáÀ.V“ît¦üüüå{íµ÷Ç--­}r©ª$ µK–¯Y³úP Р¸É•ñÉ¡‡þis³¼ÄÀ�…,˜Ã©E`æÌïÔÖ~K¥Rùãa;Y²ßžÄo_¤û­”!ØžÝv)|+~n€I/Mã§[‘µ&h…ôeÖÊÑSÌÇ”zî©{pOàV«ýgÃ/¬%ac7Ç:¸A0qWѱ¡S®Ä"ÀÙö¸%ÝXi~íÂæ‡‹AèOëAYÅœvÖ+]® ]l[ƒ¦5a Œ%‹{¡¨¨hé¤Iã7ÚÞ½% 0tX~[4Z½G,ÛÃ{Žô¢5¤xÈ;;í8º1™l“P(/¥ÚJ}}]^   z“Ù "ÒÚš’yÎû8//´&3PQÒ9 ªÓÊÊ+Gláé|Ö=S9[Ÿ¶„"óÏcó,[Oo€‰˜ØŒ«1î�‹0-’C7Õ^˜8‰«¬… –…�&zpÎðŒ[âk¹(Ä?£Ø£»GÑǂЊÉðñ#%Ocâ@&g9^obUÆQkõÀ®1®¿ëØÇIgAضVJ‘ébÌn}ˆàsi±ÿ'°̃YÍ]‡ )ùhèÐÑm}Ôâ$ÚÞ ïœ¨ªé6Ïâå-vœ°H½òêK#ž~ú™Ý—.]rdKKË.ãÇOxä7_òÏ¢T2™ì1QI$ÚdÎî3š –Çb-Ó3÷·F«ê n[0! ²Û~KüÞïXÁ”Æÿ°uÔe8Óåp&%öK`܃ëíë L= 0�´Ó<ßy›žÅQL¶çÜß Ñk=ŸýØ ‹�IºÂ4û¼X@Çší…äœnH°ZÑìTaj•¬ÃÄn¼åùüGÀ NÂ8‚°m¯”Êj ¥B°wìDH²©X;aÿ>ÐÒ:´¤du~~X[[[û{0th(qË­wÌlhhøbf‘%KâÅÅCÏ9÷ü¬Y³æðX,6MU T•åË?ÜíÞ{ïyíÜsÏ|«¾>ÙãzõÖò�(..þ¨¾¾.i�‰À;ÛÀmà,ý‹BŒ=�÷Ù:LËßÄã¥ãƒ¾›Aü0)ƒ€©Ç‘+À”‚m5øë|¾s»‡ l~K÷Ö°®Ü |ø#0ãfºÓ“°�à~Õ’¢\ñ%û›†ØcŸAÀ¿’ã‡88‚°�1„Aæ‹r(Ò +‚€ª~†idÊ(Ó”Áçˉ!%C×½—GžŽŠ©çŸî+©T*dû.d_~yÁ•Édrímž7£OÒÅì/CKJ>]ÕÅÀˆ0n3ÎÜàdL�œ¾Z)œ¡q6àþÊÀ‚îE{ï0îŽûsØïHŸm¹šãç`êP Åd\˜…œ-ÃÏþÓisUÇΠ8 còO+‹Úc¾ë±*d¢'��æ`ÓŽ«1•3á§x¸öÓNÛÙ¶­®RE¿�°€ö†AêSJõ¸“"òiMu¤Õ|GJ$Ëú+B[qqQ³ôqy..§~øÑ�Ö¯_¨9°"/™L‘@9 ¤¤dþé§Ÿþ~CC"Ô—ë(*.®ozìô[ÁTUÜ\¸Ê.øUî™Úê˜yÞ§h7Õ$!º‰Ž)�¹vÍ$)r‹u ÿ¢½1Øß»üŠ)™|jŽäÀ� (¦’ä[–,Ãdõ”{Șº½%yÀ]rðpE–ïfv mÅtPup„m 5ÕUËKË*JEäh`ÊhDFajþÃãL–ìšW³…³®R"©P(”èuþ€þÁ úø�±­­m”ˆøµ‡)/9ƒÍ{ïµ÷ÿ‡“ ±¼¾4ˆ*‡c^òá÷•Í8m{w¡©lI�Z0é{Û×h`-ÆÄz7½Oã�)I½‡½‹0þåF+DžÆ˜íûË]0Ò^ûLÆÞö§¨Äü¥ñ°z€ñcéØÐèr+&¤sÙðWíu‡ó1±itWVzqçj7Ÿûð—˜ Ëß—g™ÃÌ@ÁfLšp.8‹Žq%WÙûÑßËxÿ+L—UG¶A’­ª³ :eå•ATCŠä‹VØOàߨúIö´t‘@¨}¶í*¨¼ùÖ;C>ùäÓCé\$%ûŽöKJ†Î?á„Ê·âÁ~Ž]]èæ´c�ÄGa…Ù@XvÀh:Ìje%>Cp!ðào=8î(L@ß¡˜VÍÙp.¦Õ…ô½Zâlàß– ´ZÂÛ†1‹'èÜeóSLq¿Agf¼¯Þ ïg¼¿1Çýö±÷šOå°_1KI/¤ûX‡žàÀ,ÛŸÇ{z«‹}3ë3µ¹yãìž�[~¹qön³#“'OmÙ¸1”>ú:âñx6 �©… ½ÖB§[A3ÔcÅŽ ~ËÇøYw ‚0“¾÷Lº\š dVg`&àì¹5ÄbŒÏzo϶6«±Ð1²½ÈjsgõAøÄžgt®Soµò}HS¦I~ ƒçб_A¹»¥ŽöÙötû�‘A,îíçßäðKF»‹õ8$ãýc9žóTLWÕ4îÁ?ög?K6Ó‹ËÕÀÏØ:;‹:‚àÐ/ˆ£$üĹ „â²òÊ`´:’DdC6µ\•¼�7Ye¾§ä@)(¦ÞzûÍÃR©Ôð\¼í )%%%¯žýý³Äã‰@\ ›.iãÆ�Ã}‚#ÁÖBéuðà,Úûä"ܶA8cVõšwWw`Ì´Ga|¾“<ŸO° ÷±9?â!MßðŸ€å–,‡)vã-«=¡~�µ„œ`5н2>O+ío]‡qŸ<ìsœ2Ær5¹•ÛÞœ8#ãýý9jÌйSbÊjé]>n>V”h?ÿ¦Ì †¦Bewä`L_ŒÞ„d¼¿Çç;•À?,‘m«þŸŽ l×P´E�f…‘â¿ Ãø+“Òeo 555ŽH¥z×u¹°° õâ‹ F|öÙçG{4×n…¼Í|h=ðÀƒn3zXk}},¿/Öƒô¾�M�ã»8)Èi½Û¥šsŒ‘˜VÅÞ6iÊŸyüMV°{£øÇäpü)t ìz81ã;‹0¦Ú{1�.ó0ilz–%à÷{?ǤÛ}/C ¾‹q‘ÜF÷¾÷²Œ÷¯ °æÀ”DöâÁ¬³™¦ü…d÷¹§±ýôëèÚäßS„0õ¼x!GÒ“Ùùq&µ;̤ceÔ– «Uóðßž{÷4\Ì�#ƒ‚Ä»ðŒV%hUÓa0&JAæ×U5¿¡±a|k"è™òn¬á0ñ¸ïðD¢uBº­sW‚ÞÆB¨ªÊèÑ£ºàüs^ohhísP_:K¢©©iB‚(ÊÊ^žâ]LKàb» †ì}_€©W?¡‡Â½?,ßðѾ^¤c)a0EtÞÎ ¹T”<’Ž…o²¹gbVS{Ú.øc0.™÷û8£0¦÷tÛÇVÛþ)þ•ýÖ¥Ã2¶½6À�î~´»¢ÀÄSäêŽÙ�ÎÅŠri£|RÆûôoŠçtì#¹9úbÆûÿäHàNÎxÿ¬çþÜÛËÝ1nªK�ßÓûVŽ lc%.Ò…e@+B!Ð$† 4 ¾õÝ¥©i㤆 Ÿ‡“ÌÝ€„ÃáÔÚ5Š–ôÑI-^;•:î�îh>ÏËË«/+-¿=•"Ø—Ï›T˜Pž¾ýÖâ’X,6)�æØá¼¦zÔçªÚ[ó-¦ˆM¶…ûŸ˜Àºþ"¹ã?ïŽÖjQk5|¿…ð³^<³ùh¢ã1�™H§`š ­0ï vÇè¦]'`|Î=ðs}êüèfºu^ÆDíÓ‹ù€îÝ ¥>V‡þ„_�¤Þ„\ >çCzŠ1¥£/´÷àÛ˜ÀÄ…8 (\Îö–¶ q´Ó¢ß.ÀMõÄtûçÀÚl¼¼¹yãÔ�V¬*JÇäz ……’|üÉÇvlm�ïš.´”Þ?´ˆHÜjö^‹…Œ;öþã�?qeCC,�>š|UUóó©^|aÇD¢m¬ ~ô“·µ63¤¿ñpß>DÀûÝL£J.V/vÅ7þ™‰ÕV[=�¾¹YNÆ´ÊÞÍ#Ð¾Ü í߯Îþò~t�ò±øä ¿@ÀîöFç&eý]Itž�Eéåön‰ ¹XSFй±Ô8{¿þØÞÛ—Ûï8rà D\…³­óc�hEA=Êòl:i"‘ØeáÂ7&…{dvŒÇ ì½÷~kÃá÷“É$©Tм¼¼µcÆŒ½{öìÙ—…B¡ÏÈ(· �—úä�ƒ©nkkKLê«Æ-€¼¿xñìT*Yä#üÒï_ÙŒóq=°b [ÀDÞI÷©n%½ �bzx1Stè5L4xq?�ão1)“<–›/’{±/üš ­Àg¶ÄG°½ÑAüq¿gŸõ¹¿ýðó|¬¹Ä”qŸ/Æß:åw¾ÌçãlL­‹WìÿšÜ?Aؾ­Ž´ ºLUZ;iÔ¦¾b¡ »D£‘&„7UýÛÚ&“É‚·ß~ûàPh“i:'�¾¥%œ2y—–3¿{Ö…S§NýãìÙs.¾àü ¿~Çí·\YR2´®­­m˜·CºóèQ£ž8é¤S–%“ÉœÏÕN£¥�¹IDAT>þøãðÚµköŤJÆxˆšQÙœ�ZÖY�ù&&åpK„\0•ŽiŠ�›zþ-wcp!¦HÏÕ´Wçë-v¤^DÇÊqø›°sÁ®>Ûð±õÖ¹#Ú™Î]_Îñœ~¤²¿0‘öÔÚ4žÎqßÌy�Ÿã~~mÛë0Y ÙûÖa+‚‹AÈû"4+„¼±% ø"Æ' Æ·…•Ä+´UUÖ®]sôâÅÞ1iÒ.-±X¼ÛzéxƒææXððÃ_[Zzø-@ #ýúƼåË?Ü'•J•xŽ£¶fºÃ�8üß%%ù©úúX^_�¶£¾øÒ+ã÷ô—´ð‰ªnnsã VûíÒÓÒ]Ž1÷O·ZÿØ^0ÀDÏçû\ãLL�汘2¾—öò÷”å7ÅTá{’žµ2N ÕLlÀÖÏå‘k¬ÆÉ>Ûrq¹ìæ³m}?þ&?·ÇÓ½Ü7WwËŸmÓè2[«K #·J”΂°Ma)PïSnÙZíå�²²Ê‘V�~Y`M¦s^U%�Ç'ÿíï·~¹  m‘Ð\ymnŽëëcEõõ±‚x<.+W®(¬««Û×C 6 óñãÇß÷�SOY²aCk°~¿š>¡¶^xn^"‘Ø©‹¯.‘-Q^·¿RèzÊœBÀyV {qºìcûp�ë1™¢Ý…âw­31ÙOáß|'×5¤‰În�¹½´ÊøYJàóº{Í·+cÒT¯ÉbáñÃ8LUË´àÌD¬—÷ãétÎ~É´´�{ÉèLK@W~<&k'›e¨·þkv|Aؾ Ê Í’J¦é‡JLšW4Y‹Ø$Ú.Ò–aÉ’¥ß¬ýàá……á”i�œÛÂá ,) §ž{î…�ãñø,/‰H¥R’ŸŸ¿òÛßþî? Bª)É `ì;�ÂÂÂä›o½_²bÅŠ“ÓŸ±H(òT´:Ò¸ Mo®ã_ˆIázÝ ‘C€]¬àyÕjõçø,Ú=û&ŸüL‘™lD¡SÈçö^þæ7­Åâ2:90A˜'÷ðxC³Ü�ÂtŸm]¥Þío…æ÷³|þžÏ=óML°ÞávÛNY,L=Á({ÝAçT×L+@®õfÒ9Ðu‰Ï÷˜L�<¿eGŸïõ””æcZOÿ—þèÂöˆšh$%ð´Uö5c¥H«í§y6ߤ¬Í]3-±XË´›þò—ÒpX’VÐæ,DÒV‚@€¶7ß\tH2™ {[9‹S¦L½ý ÷®kjŠûZ51�ÆXP@ëÍ7ß|R<Ÿš®Ãà#i×ѳö±ÛÊ35Suîdzk>&hínŒÉ~?�†ô’�Ðï)V[-nž]X?Êò½CéÜP¨;¼ƒq‰ÝkîÌô½‘À•t¬å�‹ÈÄN8§~ç›…h]n…í4²÷Lðº'&azLÜiÞ�]Ω=¸æ-á8ÙÎI,ƒlevH|:Çãîë³­9ƒì|Åžû*û÷®.~Óôü¦]1–¶abl~褉#Û«!ŠHÂGØÚ÷òå²òÊYÑêÈà1?±okÞòåËNy衚 ¶e³ô„$äçç§Ö®m(XûÙÚ#Úkv/((|ûÄOx¢¡¡5dyC_ƒðtذpâ¶ÛîšµråŠ3¼–�Šœ¾ö§kª#onc3ÛÝøXrpˆçù[‰©ÕðuLk]/B}° db¦&Á�˜÷™fòaVãï jéh&>�Î%‘§Úóåóä§î>€s:ÊgÛ‘s^ŠqüÒÎëÕ˜}²�‹BLÕÌwí¾�`²7žðÜ'™8&‡k�Œ)Ru&hô÷t.:µ?ÆUàÅS9ŽÅ\Ÿmé8”£-9ú·µ†]nïóU]È›\ʆ-Xd¯ý_v,ppa{D4Zõ–G;ìœÞ'”¨rš‡ü?DS¶‰c¦«�X,>ëÁHä”ÂÂPÒH÷œê"(@QQ q×?þwv<Ÿî©ª¨"ÒºóÎ;?¸÷^ûl°E‘úè§W ó¨­ý°èÑÇ='™LŽÈRÃAPÚ@oèá ‚[ÁÔv7îÇÑ93a!&5Ñ¡^Xð9G¦EádàZ:îÒÓIÍx¿ÓA2sñ>Ð^W.kN]ëÆæDWæ{¿¶ëWcz3œc5ô¨Õ†?±V˜ŸàŸ®‰}î?¶–•¦�st('îW„é"LœB6Òq5Æuy¦nÄaÀÏ}æ8Ó½�kü´óâ1;ï�Øy®µÄà×ç®÷Ù÷¿ðÈL?K_Æd}üÉÎÑo-É�áàÂvŽÛmXA'©¨"ZYV^±³!<¯*�øIŸ´à^½zõiW\qåGŒ(lM¥Œ@îZ\kZè§ÞzëÍ#­ù?½�„Ãáe•Ç=é¹MúÔ�I ’zÃ�7V666~¡ýò¥Ã׬ƒãÞhuÕó=ÛNÄßõЄîLL%¿lhÉ"�oøÒ•ïµ–ŽtÑ ½²oGŒëe�ýÎõ>DËÏ 4cZ/³s1ÌZ2nµ„à¿í|üÉ^Ç3YÎïWÿ 5Çñò ž,´V®³V¿Ì†w²<³ó­µeŒ%cÓ1©�/Y‹ÊÞ˜ Ûãì<¥ppaàa�•ÆœßQ˜‹Š2 äØÒ² ‰VWmá&U6¨öƒ¦R©Ðk¯¿zÉ_o¹möˆ ûµ.IB8Ö7½;¢®®î oOUÕI“&Ý{Øa_XÓÖÖÖ—šH›ÜÇ$nºé/s—-[ö}UÍïT­ÑT‚`½ ¿íŹülÆùãsüÞÔ˜ÛÅg™¹êã1füјàÃÌÀ¯Ýì5ìŒ)éÜ•E •ΩsOwñý9tކÏFÊÎÀ”Ìõ"Ï.òwucíy=‹@>½Ÿço:¦Dð_»Ñ »ë²ÓPè«ã™Í-Òl­“½ïE¶Ú301 �V# ãž*´¿á«•7u±æÏëÁœg"[ÇÏwíï¹€ì%¨«³l Ül­1L�‰?Ó^ "í~y؉ G>S•{DSþÀ#,BŠžÙ.øô?"Ô˜�èT 9h[[Û¨GyäÒÛïøç®Ã†…“Æ|ŸÍ’ �ª©‰ÎI$¬[AS©……o_pþ�ŠÇ õ¤Œs6+Űaáøõ×ß´çüçž½<›kA Ýâúšêªž6 š‚Õô4éÞ¢‚Î~õ9tît×�µ7&Ò|®%Ga2^¦sÀá˜ÔÁ7íÂ^•ARÖzþ=Ûj³¿´Úf&N§c�Ør{¼løª�ö? ÿèôuÖZòa&'Åô~x ÿ€·´@ðÃøöìõä-L�ƒÓ0~ólXÐÅgÕvœïò!ã~Úö£öù#]§nÞڃ߳„z Ý» fÒ1Xp==k!­>§_YKHwõn%{Iq?¬²¿«Œ�­¤é‚ÖG´:¢÷Ÿ‘áÞ÷´+š+b‚�¢ÕUq…߀¬D3bÒÂ8‹Í|è¡È%÷Ý÷ЎÇ$R)|I‚Ú¼1XºtéþXómÚ‚0wîÜ›'MßÜÒëƒ_ßœsذpâÚkoÜ÷‰'Ÿø]"‘Ø1� ‘Å*ñŠölq c*>d…k&&[-ñ-»8_…!žžâr:·ÿMkøÿƘF¯§säö_èlÖf…õ|L ߨÕ0÷Ã?`¯Àž'lh¯ ùà nQU†öô¥—^ö÷ÖVÍK¿‡Vc‘ùù¢×]wãÞÏ<û̉Db§,i’émÍÀk¢U¹6÷kµèÓºÑ,‹ìçS1fÐrÚ+Uöæ™yÌjÓÙr¸‹­`>À �ûi7ù6bLÒ‡ÐÙ<Ÿo· Æý8&n¶ÕÒ½ãö‰] ¿NǸ�|{Î� ×ÀNv &c‚Ýò­Pùã;¾Åç·bâ Á?X/Mp&bLêÇcLÁ逴Åv¿ý}ö/ðÌËöü­–ð´áµ¿³Õ(çç¨�е ýÜZiŽÅÄ8œŽ©ô˜K��åö8Xáu�½ç^ÏA�·äç$zÞSä?–\M²ó×b ä�˜ÀÆK-Ñë‰O~'{ý—Ù}{ZK`£%…ß°d»¾‡û/ÇÄ¡”XrZlŸû÷ìsò+K*_ÄÕ9pˆ‚�GiYÅ‘"r�óx…ç³ÀqÑêHƒÝ繨+9�J¥$ ­™3gÎ5ÿuÞùO�=2ÑÔÔnƒzÇ·ÍzôÑGþ”L%G��4.¼hA,Öè7Pç(:lX¸mé²…¾ñÏ¥Ô~pA2™Þ 9øM´:ғ؃°Õ\‡bмÄì«Å3”ñʳÂçuLaŸÞâ +“˜(íVÏKìyò,™hÀ¿ÉÔ°öÇD�O°×ßfʵ3ÿ_<‹o!&ž`¶%ïbÜÙ å[Mí˜à±öé1h² }ÆqyZ] ð ½Î�ý鱎ÛëÏ K2+äÝ€I¡ Ú1kóŒYãNùa†•á)KLü�î±Âæ5L€Ú±œˆ1ã„I½K»žÞµÚî¿ØÊŒÛƒÌpZ½#[�$ü¯ˆœ–Mð¢’BøpI´:’*+¯¥èߩ̲›´ø´¡ l5jTõÉ'�ò·c�=jEKKª k „Ãù©+ÿ»C_{í•kÒÖ¤aC‡>qñÅ—üj„�Z½Ö‰niˆ*†“’¸ýöÌ|ì?�}oÆ ‡g$Š?Ò›U9¯&Zµ­h i²Ñ×…2„ $l°ä@»y^µ—Ïù+ìÃÖúPß˵¢/¿w¤µj4’[Õ¾ÑÖŠÒ—’&¦áFû×EÀ;8äçbØ:ÈÁn"r6ÊxÊ&€ö‘7kk×ÖÖ.n™6}Æ‹‚†1Ïv  ùÍÍÍ»-|cá᯼²0±ï¾{/3fhk,ÖX¿~]êý÷ß?¸-Ù6< 6Ì›7ïêãŽ-]ÙÚš”îÈAš’—ÔaíË>\Q|ño.ýÆ‚/ý²¹¹y ¯Ý£‘q<¦( çÕD«š·¡iKõãqÖ³ù›mÄÔøŒ�Ë!o±–Š\µ÷fL|ÀHL §ô`L_À¸M¾‡‰�¯uZ¯ƒƒ³ l;Ä ¼"_�rLÞòŽ~–€ÎâT>(�VGÛcì**UÀ,»g—ր¢7§OŸ~×QGùê>ûìYÿðÃ�L|ù•—÷�5k··Ï>ëÛïmØÏË–¹`û7h  //¨ªÂ† kó^}ÑÈÇâàe.ýFº|re™­Ñ@Ad>è×£ÕU.JÙ¡+LÁÄ މÄaï­LÀàLÀãŒ;®Þ ™ƒƒ#Û*9'*ÿ�ðCE $·©H Ü7N®©Ž,1Ǫœ�ê-"ò…®9†!é~ápøƒáÃG,ØeÒ¤—÷Ùwï÷æxÐú‘£†%<Ú±_¼@ð½÷–yå•ãÞ{ñôO>YµO}}ýþ‰DbU%l:—O ƒ÷«�ïG«#»;ÂÁÁÁÁ„A�²òÊýUõZ9ДDÎîŸï$\Umã}I�S£Õ‘JË*'!ú'AŽóò®­íV�æ¼¼¼úP(ÿÓ¢¢¢‡ ²rذ¡kŠ‹‡l(çÇ$‹…›†746ìÐÔÔ´sssˤD¢u|2™¦ª…žce#¤cÔt[¸ øeM´ªÎÝŽ 8rP^¹ð ÊN]›à;ÿ;óóg�S£Õ‘Uöتz�ˆœã‰.ÌjRH[ÒßQÕMÝ­°ïôý4¬• +KA¦ÕAL¿j‰‰‰$ÿŸhu¤µ}\*$Z]å|ÄŽ R‚PVùÂÑFVf'R=‹�W@gE=]KË*N‘_±©J^nÙ]ˆ}–…í(ð6ªG£UzÆcOEO‘éÀ§(EŸ¯‰V¹44‡„ËbØ‚(-«ØID®B]‘ËÜ¢˜¼í™Ù­ ¦ðÉÑÓ¦ÏX][»ø€%µ‹ßÜuúŒÇ ³AòéÆšàË» Ý“)QÖ‹ppa4ZµÀcM9G…›D¤Ø]á *Eä³ÚÚÅ Ýãàààà,ƒ… LBd±øW¤K ï„¢W׌Tä_¨ÔE‘!3�J#¢×�\­Ž´\‘§Ê¾ ¿ŽÙ¤úo‘¹7§÷ \�ÈÛ5Õ‘@iyåHQ½äT œ™}¡ªŸû×D]fƒƒƒƒƒ#ƒƒ ™/íía;ª²R„G«#ÿòhÙ³�ûPfdKcÌØöÊoE«#ê9Îq˜¦ûcªê»8FÏ8@ÇýÚ0…pžýC´ºjþ¦ß_^™'¦¡ÌµŠîã5¢$“I‚Á çÀzRMuÕ}î®qppp8ÃÄ’ÚÅ:múŒ�E9‘!´—§UЇEä»ÑêÈ“Þ}jk6múÌçÂDµ©D/-¬'#œ ÙuúŒ•Kj¯·Çù`Úô™wajدSÕH± …*¦ZQ»tö Yè@ ÔœN„MQŽkÅÔš¿å×Ñhä�µµ‹WxÈÑî"ò à�ª:É™J¥X¿~ÅÅÅžÉýi—‰ƒƒƒƒƒ³ +‚‹ð]”Ùˆ|ܧª÷ÕD«²w)+¯œÜúE#A»ÌH†ZEÿ)È_£Õ‘O2Ž7t2È>Š(ÈžŠî"Ȱ.Í íê€e¨¾®È‹  VD£Uë3Î3EMá“Ä4�!3@³©©‘P^ˆpA�íÿ¨rh4yÆÝ1Ž *”•W jÆ?�äT¶·´¬bGà69’nª.b** *²Z”Ûn‰VG–ûÛTu LD™¨è8‹…¶€¬Wt�À ��Uu]¶Þ eå•»aˆÁ©ª:ÂÄP¨ Ò�$“IR©$¡P~&!y åðh4²ÁÝ-Ž 8tO,†`j̃ìíw;R…ôL«6!Å´U}X«ª 5ѾÕ(+¯ CQv@8åD„£,¹@²§tf½jëäø—ª~gëÑààààà‚ÃÀ ´¬"("g€þdb«AW4ÁôrHoúå=E‰ð6°H[šs )£ŒµAwEd°§*³vì§›J�”É€àoÀ¥ƒƒƒƒƒ#] ê²Ê9D9ÒÎfo2R¨Æ�˜ÂFYú9J"�ª´Ú²MùC�ŠŽd¤B±@J>ðœ<‹�7ÖÒ/•FZ€ïD«#w»wppppÁ!W’`Ìûç?ÆÐ^…±§d¡ýû™èPø¹C†CÏÏÑNt¹ªÔƒîåWxIÛ�_/pT´:òŠ›qGzFf ‡è¸­jz;Å è Uî‘ë€|ÐûAö$[�UQ‘*Òm®6/\„í¦^ÂŒGžA‰3)ð´v8Æ éΑ²åÏ?áÑꪆÚÚÅuÓ¦Ï\‚j9H‘�›DQ�Ñ(3¦MŸYS[»Ø-:8888 ‚COa+6N88½Åmw…ÂÇ"ÜüeU4Ú9à°´¬â ¹Í6�ê\~AMë Q¹á‚huÄ5srppppÁ¡·(+«‹pÈW€}1�†ýO:¾�¡¬Waª÷ˆÈƒÑêH}·×Z^ù[à׾Ƕn »ñ+ÑêÈýnvApè²P^yè‰>Óî×bÚ£½{â³—a^ ,Eu"//ïG«s+e¬•"üYáÛYnÎô%>­ŽéfÔÁÁÁÁ‡¾[BÀˈÌÍò•Và5`ÊH !ZR�V”fEëDdè2UyOÐ÷–€|4F«#‰>\çN w‹ÈÁdÉ’På�­®J¹™upppØ<ÈsC0h¨àT�É]d&¾­ŽÌ++¯ÌC¢èA €��2L Ä6 lì è ÑhÕÇeå•ç�`šS¥-›­µŽ88888‚àÐP•= éh| /D«#m@½} ¢Õ‘E¥eß‘Ã7]¯¹Ôfàz7£›7ƒÆ‚°B°³ùÀˆi½Õ &ZUô4àU Mє»À€ÇÝ„:8888‚àÐG”•U¡º›j约á#�À[Ûu×TW=¬ªGû  "_PåΞ>:8888ôÎÅ0 ‘áÚy»Š©p¸TUWm�×^­Ú,t³èàààà,ý 6‚®ÚÔH)½=ý¼&HÌ�”ƒƒƒƒƒ#ƒÑêH+Èí 1S�M…Uýg4iu#åàààà�†ëÅ0HP[»xé´é3— ¬ª%"" ŸŠÊ�j¢U7B^¸BIƒ eå£Qö·Óÿj4YëFÅÁÁÁÁ!ÿ„²ÔgOãå‰IEND®B`‚pallets-eco-flask-caching-93fd601/docs/_templates/000077500000000000000000000000001524661224300220425ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/docs/_templates/sidebarintro.html000066400000000000000000000003221524661224300254120ustar00rootroot00000000000000

Useful Links

pallets-eco-flask-caching-93fd601/docs/api.rst000066400000000000000000000036231524661224300212140ustar00rootroot00000000000000API === This section contains the API documentation of the Flask-Caching extension and lists the backends which are supported out of the box via cachelib. The :ref:`Configuration ` section explains how the backends can be used. .. currentmodule:: flask_caching Cache API --------- .. module:: flask_caching .. autoclass:: Cache :members: cache, init_app, get, set, add, delete, get_many, set_many, delete_many, get_dict, unlink, has, clear, cached, delete_cached, memoize, delete_memoized, delete_memoized_verhash .. autoclass:: CachedResponse .. autofunction:: make_template_fragment_key Signals ------- The following signals are supported: .. data:: cache_view_hit Sent when a view decorated with :meth:`~Cache.cached` is served from the cache. It is passed ``cache``, the :class:`Cache` instance, ``cache_key``, the key the response was found under, and ``args`` and ``kwargs``, the arguments the view was called with. .. data:: cache_view_miss Sent when a view decorated with :meth:`~Cache.cached` is not found in the cache and has to be called. It is passed the same arguments as :data:`cache_view_hit`. .. data:: cache_memoize_hit Sent when a function decorated with :meth:`~Cache.memoize` is served from the cache. In addition to the arguments passed to :data:`cache_view_hit`, it is passed ``f``, the undecorated function. .. data:: cache_memoize_miss Sent when a function decorated with :meth:`~Cache.memoize` is not found in the cache and has to be called. It is passed the same arguments as :data:`cache_memoize_hit`. By default, signals are disabled. To enable sending signals set ``CACHE_ENABLE_SIGNALS`` to ``True``. See the `Flask documentation on signals`_ for information on how to use these signals in your code. .. _Flask documentation on signals: https://flask.palletsprojects.com/en/latest/signals/ pallets-eco-flask-caching-93fd601/docs/backends.rst000066400000000000000000000147161524661224300222220ustar00rootroot00000000000000Built-in Cache Backends ----------------------- .. _basecache: BaseCache ````````` .. currentmodule:: flask_caching.backends.base .. py:class:: BaseCache :no-index-entry: Baseclass for all the cache backends listed below. It isn't meant to be used directly, but you can subclass it to implement your own :ref:`custom cache backend `. .. _nullcache: NullCache ````````` .. currentmodule:: flask_caching.backends.nullcache .. py:class:: NullCache :no-index-entry: Set ``CACHE_TYPE`` to ``NullCache`` to use this type. Cache that doesn't cache - CACHE_DEFAULT_TIMEOUT .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _simplecache: SimpleCache ``````````` .. currentmodule:: flask_caching.backends.simplecache .. py:class:: SimpleCache :no-index-entry: Set ``CACHE_TYPE`` to ``SimpleCache`` to use this type. Uses a local python dictionary for caching. All operations are protected by a lock, making it thread-safe. Relevant configuration values - CACHE_DEFAULT_TIMEOUT - CACHE_IGNORE_ERRORS - CACHE_THRESHOLD .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _filesystemcache: FileSystemCache ``````````````` .. currentmodule:: flask_caching.backends.filesystemcache .. py:class:: FileSystemCache :no-index-entry: Set ``CACHE_TYPE`` to ``FileSystemCache`` to use this type. Uses the filesystem to store cached values - CACHE_DEFAULT_TIMEOUT - CACHE_IGNORE_ERRORS - CACHE_DIR - CACHE_THRESHOLD - CACHE_OPTIONS There is a single valid entry in CACHE_OPTIONS: *mode*, which should be a 3 digit linux-style permissions octal mode. .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _rediscache: RedisCache `````````` .. currentmodule:: flask_caching.backends.rediscache .. py:class:: RedisCache :no-index-entry: Set ``CACHE_TYPE`` to ``RedisCache`` to use this type. - CACHE_DEFAULT_TIMEOUT - CACHE_IGNORE_ERRORS - CACHE_KEY_PREFIX - CACHE_OPTIONS - CACHE_REDIS_HOST - CACHE_REDIS_PORT - CACHE_REDIS_PASSWORD - CACHE_REDIS_DB - CACHE_REDIS_URL Entries in CACHE_OPTIONS are passed to the redis client as ``**kwargs`` ``CACHE_REDIS_HOST`` also accepts an already created Redis client instead of a host name. Use it to share one connection pool with the rest of the application, see :ref:`sharing-a-redis-client`. .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _redissentinelcache: RedisSentinelCache `````````````````` .. py:class:: RedisSentinelCache :no-index-entry: Set ``CACHE_TYPE`` to ``RedisSentinel`` to use this type. - CACHE_IGNORE_ERRORS - CACHE_KEY_PREFIX - CACHE_REDIS_SENTINELS - CACHE_REDIS_SENTINEL_MASTER - CACHE_REDIS_PASSWORD - CACHE_REDIS_SENTINEL_PASSWORD - CACHE_REDIS_DB Entries in CACHE_OPTIONS are passed to the redis client as ``**kwargs`` .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _redisclustercache: RedisClusterCache `````````````````` .. py:class:: RedisClusterCache :no-index-entry: Set ``CACHE_TYPE`` to ``RedisClusterCache`` to use this type. - CACHE_IGNORE_ERRORS - CACHE_KEY_PREFIX - CACHE_REDIS_CLUSTER - CACHE_REDIS_PASSWORD - CACHE_REDIS_URL Entries in CACHE_OPTIONS are passed to the redis client as ``**kwargs`` .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _memcachedcache: MemcachedCache `````````````` .. currentmodule:: flask_caching.backends.memcache .. py:class:: MemcachedCache :no-index-entry: Set ``CACHE_TYPE`` to ``MemcachedCache`` to use this type. Uses a memcached server as a backend. Supports either pylibmc or memcache or google app engine memcache library. Relevant configuration values - CACHE_DEFAULT_TIMEOUT - CACHE_IGNORE_ERRORS - CACHE_KEY_PREFIX - CACHE_MEMCACHED_SERVERS - CACHE_OPTIONS ``MemcachedCache`` only accepts ``pool_size`` and ``pool_blocking`` in the ``CACHE_OPTIONS``. .. versionadded:: 2.5.0 ``pool_size`` and ``pool_blocking`` options. .. note:: Flask-Caching does not pass additional configuration options to memcached backends. To add additional configuration to these caches, directly set the configuration options on the object after instantiation:: from flask_caching import Cache cache = Cache() # Can't configure the client yet... cache.init_app(flask_app, {"CACHE_TYPE": "MemcachedCache"}) # Break convention and set options on the _client object # directly. For pylibmc behaviors: cache.cache._client.behaviors({"tcp_nodelay": True}) Alternatively, see :ref:`Custom Cache Backends `. .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. _saslmemcachedcache: SASLMemcachedCache `````````````````` .. py:class:: SASLMemcachedCache :no-index-entry: Set ``CACHE_TYPE`` to ``SASLMemcachedCache`` to use this type. Uses a memcached server as a backend. Intended to be used with a SASL enabled connection to the memcached server. pylibmc is required and SASL must be supported by libmemcached. Relevant configuration values - CACHE_DEFAULT_TIMEOUT - CACHE_IGNORE_ERRORS - CACHE_KEY_PREFIX - CACHE_OPTIONS - CACHE_MEMCACHED_SERVERS - CACHE_MEMCACHED_USERNAME - CACHE_MEMCACHED_PASSWORD .. note:: Like ``MemcachedCache``, ``pool_size``/``pool_blocking`` in ``CACHE_OPTIONS`` configure the connection pool. Unlike ``MemcachedCache``, any other ``CACHE_OPTIONS`` entry is also accepted and forwarded as ``**kwargs`` to ``pylibmc.Client``. .. versionadded:: 0.10 .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. .. versionchanged:: 2.5.0 ``CACHE_IGNORE_ERRORS`` is now honoured by this backend. .. _spreadsaslmemcachedcache: SpreadSASLMemcachedCache ```````````````````````` .. py:class:: SpreadSASLMemcachedCache :no-index-entry: Set ``CACHE_TYPE`` to ``SpreadSASLMemcachedCache`` to use this type. Same as SASLMemcachedCache however, it has the ability to spread value across multiple keys if it is bigger than the memcached threshold which by default is 1M. Uses pickle. .. versionadded:: 0.11 .. versionchanged:: 1.1.0 Renamed ``spreadsaslmemcachedcache`` to ``spreadsaslmemcached`` for the sake of consistency. .. versionchanged:: 2.5.0 Removed the deprecated old name in favour of just using the class name. pallets-eco-flask-caching-93fd601/docs/changelog.rst000066400000000000000000000000341524661224300223630ustar00rootroot00000000000000.. include:: ../CHANGES.rst pallets-eco-flask-caching-93fd601/docs/conf.py000066400000000000000000000033231524661224300212050ustar00rootroot00000000000000from pallets_sphinx_themes import get_version from pallets_sphinx_themes import ProjectLink project = "Flask-Caching" copyright = "2016, Thadeus Burgess, Peter Justin" author = "Thadeus Burgess, Peter Justin" release, version = get_version("Flask-Caching") extensions = [ "sphinx.ext.autodoc", "sphinx.ext.extlinks", "sphinx.ext.intersphinx", "pallets_sphinx_themes", "sphinxcontrib.log_cabinet", ] autodoc_member_order = "bysource" autodoc_typehints = "description" autodoc_preserve_defaults = True # Show just the class name in signatures instead of the full dotted path. add_module_names = False extlinks = { "issue": ("https://github.com/pallets-eco/flask-caching/issues/%s", "#%s"), "pr": ("https://github.com/pallets-eco/flask-caching/pull/%s", "#%s"), } intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "flask": ("https://flask.palletsprojects.com/", None), } issues_github_path = "pallets-eco/flask-caching" html_theme = "flask" html_context = { "project_links": [ ProjectLink("PyPI Releases", "https://pypi.python.org/pypi/Flask-Caching/"), ProjectLink("Source Code", "https://github.com/pallets-eco/Flask-Caching"), ProjectLink( "Issue Tracker", "https://github.com/pallets-eco/Flask-Caching/issues/" ), ] } html_sidebars = { "index": ["project.html", "localtoc.html", "searchbox.html"], "**": ["localtoc.html", "relations.html", "searchbox.html"], } singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]} html_static_path = ["_static"] html_favicon = "_static/flask-cache.png" html_logo = "_static/flask-cache.png" html_title = f"Flask-Caching Documentation ({version})" html_show_sourcelink = False pallets-eco-flask-caching-93fd601/docs/configuration.rst000066400000000000000000000350321524661224300233110ustar00rootroot00000000000000.. _configuring-flask-caching: Configuration ------------- The following configuration values exist for Flask-Caching: .. tabularcolumns:: |p{6.5cm}|p{8.5cm}| ================================== ================================================================== ``CACHE_TYPE`` Specifies which type of caching object to use. This is an import string that will be imported and instantiated. It is assumed that the import object is a function that will return a cache object that adheres to the cache API. For flask_caching.backends.cache objects, you do not need to specify the entire import string, just one of the following names. Built-in cache types: * :ref:`NullCache ` (default) * :ref:`SimpleCache ` * :ref:`FileSystemCache ` * :ref:`RedisCache ` (redis required) * :ref:`RedisSentinelCache ` (redis required) * :ref:`RedisClusterCache ` (redis required) * :ref:`MemcachedCache ` (pylibmc or memcache required) * :ref:`SASLMemcachedCache ` (pylibmc required) * :ref:`SpreadSASLMemcachedCache ` (pylibmc required) User contributed cache types: * :ref:`GoogleCloudStorageCache ` (google-cloud-storage required) * :ref:`UWSGICache ` (uwsgi required) ``CACHE_NO_NULL_WARNING`` Silence the warning message when using cache type of 'NullCache'. ``CACHE_ARGS`` Optional list to unpack and pass during the cache class instantiation. ``CACHE_OPTIONS`` Optional dictionary to pass during the cache class instantiation. ``CACHE_DEFAULT_TIMEOUT`` The timeout that is used if no other timeout is specified. Unit of time is seconds, a ``datetime.timedelta`` is also accepted. Defaults to ``300``. ``CACHE_IGNORE_ERRORS`` If set to ``True`` any errors that occurred during the deletion process will be ignored. However, if it is set to ``False`` a ``RuntimeError`` is raised if the key couldn't deleted. Applies to every backend. Defaults to ``False``. ``CACHE_THRESHOLD`` The maximum number of items the cache will store before it starts deleting some. Used only for SimpleCache and FileSystemCache. Defaults to ``500``. ``CACHE_KEY_PREFIX`` A prefix that is added before all keys. This makes it possible to use the same memcached server for different apps. Used only for RedisCache and MemcachedCache. Defaults to ``flask_cache_``. ``CACHE_SOURCE_CHECK`` The default condition applied to function decorators which controls if the source code of the function should be included when forming the hash which is used as the cache key. This ensures that if the source code changes, the cached value will not be returned when the new function is called even if the arguments are the same. Defaults to ``False``. See :ref:`source_check `. ``CACHE_HASH_METHOD`` hash_method used for hashing cache keys. Defaults to ``hashlib.sha256``. Changing it invalidates all existing cache entries. ``CACHE_SERIALIZER`` The serializer the backend uses to serializer the values. If ``None`` or not set, it uses the default serializer from the backend. See :ref:`serializers` for more information. ``CACHE_ENABLE_SIGNALS`` Send Flask Signals for :meth:`~Cache.cached` and :meth:`~Cache.memoize` cache hits and misses. ``CACHE_UWSGI_NAME`` The name of the uwsgi caching instance to connect to, for example: mycache@localhost:3031, defaults to an empty string, which means uWSGI will cache in the local instance. If the cache is in the same instance as the werkzeug app, you only have to provide the name of the cache. ``CACHE_GCS_BUCKET`` The name of the Google Cloud Storage bucket to use. The bucket must already exist. Used only for :ref:`GoogleCloudStorageCache `. ``CACHE_MEMCACHED_SERVERS`` A list or a tuple of server addresses. Used only for MemcachedCache ``CACHE_MEMCACHED_USERNAME`` Username for SASL authentication with memcached. Used only for SASLMemcachedCache ``CACHE_MEMCACHED_PASSWORD`` Password for SASL authentication with memcached. Used only for SASLMemcachedCache ``CACHE_REDIS_HOST`` A Redis server host. Used only for RedisCache. May also be an already created Redis client, see :ref:`sharing-a-redis-client`. Ignored if ``CACHE_REDIS_URL`` is set. ``CACHE_REDIS_PORT`` A Redis server port. Default is 6379. Used only for RedisCache. Ignored if ``CACHE_REDIS_URL`` is set. ``CACHE_REDIS_PASSWORD`` A Redis password for server. Used only for RedisCache and RedisSentinelCache. Ignored if ``CACHE_REDIS_URL`` is set. ``CACHE_REDIS_DB`` A Redis db (zero-based number index). Default is 0. Used only for RedisCache and RedisSentinelCache. Ignored if ``CACHE_REDIS_URL`` already contains a database, e.g. ``redis://localhost:6379/2``. ``CACHE_REDIS_SENTINELS`` A list or a tuple of Redis sentinel addresses. Used only for RedisSentinelCache. ``CACHE_REDIS_SENTINEL_MASTER`` The name of the master server in a sentinel configuration. Used only for RedisSentinelCache. ``CACHE_REDIS_SENTINEL_PASSWORD`` A password for authenticating with the sentinel servers themselves, as opposed to ``CACHE_REDIS_PASSWORD`` which authenticates with the master. Used only for RedisSentinelCache. ``CACHE_REDIS_CLUSTER`` A string of comma-separated Redis cluster node addresses. e.g. host1:port1,host2:port2,host3:port3 . Used only for RedisClusterCache. ``CACHE_FILE_HASH_METHOD`` hash_method used for hashing the file names of cached entries. Defaults to ``hashlib.sha256``. ``CACHE_DIR`` Directory to store cache. Used only for FileSystemCache. ``CACHE_REDIS_URL`` URL to connect to Redis server. Example ``redis://user:password@localhost:6379/2``. Supports protocols ``redis://``, ``rediss://`` (redis over TLS) and ``unix://``. See more info about URL support `here `_. Used only for RedisCache and RedisClusterCache. Takes precedence over the individual connection settings. ================================== ================================================================== Flask-Caching will always use the `CACHE__URL` (if available) if a complete connection URI is provided. For example, ``CACHE_REDIS_URL`` and the individual connection settings are two alternative ways of describing the same connection. They are not merged: if ``CACHE_REDIS_URL`` is set, the connection is built from the URL alone and ``CACHE_REDIS_HOST``, ``CACHE_REDIS_PORT`` and ``CACHE_REDIS_PASSWORD`` are silently ignored. This is intended behaviour, not a bug. For example, this configuration connects without a password, because the URL contains a database (``/0``):: config = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6379/0", "CACHE_REDIS_PASSWORD": "hunter2", # ignored } Put the credentials in the URL instead:: config = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://:hunter2@localhost:6379/0", } Or drop the URL and use the individual settings only:: config = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": "localhost", "CACHE_REDIS_PORT": 6379, "CACHE_REDIS_DB": 0, "CACHE_REDIS_PASSWORD": "hunter2", } .. _sharing-a-redis-client: Sharing a Redis client or connection pool ````````````````````````````````````````` ``CACHE_REDIS_HOST`` does not have to be a host name. If it is set to anything other than a string, that object is used as the Redis client as-is. That makes it possible to hand ``RedisCache`` a client that was created elsewhere, so a single connection pool is shared with the rest of the application instead of every extension opening a pool of its own:: import redis from flask import Flask from flask_caching import Cache pool = redis.ConnectionPool( host="localhost", port=6379, db=0, max_connections=20 ) redis_client = redis.Redis(connection_pool=pool) app = Flask(__name__) app.config.from_mapping( { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": redis_client, "CACHE_KEY_PREFIX": "myapp_", } ) cache = Cache(app) The same client, or the pool it was built from, can then be passed to other extensions such as Flask-Limiter or Flask-Session and be used by the application directly, instead of each of them connecting on its own. Any object with a `redis-py`_ compatible API is accepted, not only ``redis.Redis`` instances. A client returned by ``redis.sentinel.Sentinel.master_for()`` works as well. A few things to keep in mind when passing in a client: - Do not set ``CACHE_REDIS_URL``. It takes precedence and builds a new client from the URL, which silently discards the one that was passed in. - ``CACHE_REDIS_PORT``, ``CACHE_REDIS_DB``, ``CACHE_REDIS_PASSWORD`` and ``CACHE_OPTIONS`` are only used to build a client from a host name. They are ignored here; configure them on the client instead. ``CACHE_KEY_PREFIX`` and ``CACHE_DEFAULT_TIMEOUT`` still apply, they are handled by the cache itself. - The client must not be created with ``decode_responses=True``. Cached values are pickled bytes and reading them back through a decoding client raises a ``TypeError``. That option is only rejected with an explicit error when Flask-Caching creates the client itself. - Flask-Caching does not take ownership of the client. It never closes it or its connection pool, so its lifetime is up to the application. - This applies to ``RedisCache`` only. ``RedisSentinelCache`` and ``RedisClusterCache`` always create their own clients from the configuration values. Using a cachelib backend directly ````````````````````````````````` The built-in backends subclass their `cachelib`_ counterparts, so ``CACHE_TYPE`` can also be an import string pointing straight at a cachelib class. This is the way to use a backend that cachelib ships but Flask-Caching does not wrap, such as ``MongoDbCache``, ``DynamoDbCache`` or ``ValkeyCache``. For example, a MongoDbCache can look like this:: config = { "CACHE_TYPE": "cachelib.MongoDbCache", "CACHE_ARGS": ["mongodb://localhost:27017"], "CACHE_OPTIONS": {"db": "myapp", "collection": "cache"}, } and a ValkeyCache config like this:: config = { "CACHE_TYPE": "cachelib.valkey.ValkeyCache", "CACHE_OPTIONS": { "host": "localhost", "port": 6379, "db": 0, "key_prefix": "myapp", }, } The class is then instantiated directly: ``CACHE_ARGS`` is passed as positional arguments and ``CACHE_OPTIONS`` as keyword arguments, together with ``CACHE_DEFAULT_TIMEOUT``. The other ``CACHE_*`` options (``CACHE_DIR``, ``CACHE_THRESHOLD``, ``CACHE_KEY_PREFIX``, the ``CACHE_REDIS_*`` settings, ...) are only read by the built-in backends and are ignored here. Pass the equivalent cachelib arguments through ``CACHE_OPTIONS`` instead. .. _cachelib: https://github.com/pallets/cachelib .. _redis-py: https://redis.readthedocs.io/ pallets-eco-flask-caching-93fd601/docs/contrib-backends.rst000066400000000000000000000047421524661224300236560ustar00rootroot00000000000000Contributed Cache Backends -------------------------- .. warning:: The backends in this section were contributed by users of Flask-Caching. They are not officially supported: this project does not guarantee that they are maintained, tested or functional at any given time. Use at your own risk. Unlike the built-in backends, these cannot be selected by their class name alone. Set ``CACHE_TYPE`` to the full import path of the class. .. _googlecloudstoragecache: GoogleCloudStorageCache ``````````````````````` Set ``CACHE_TYPE`` to ``flask_caching.contrib.googlecloudstoragecache.GoogleCloudStorageCache`` to use this type. Uses a Google Cloud Storage bucket as a cache backend. The ``google-cloud-storage`` package is required. Relevant configuration values - CACHE_DEFAULT_TIMEOUT - CACHE_KEY_PREFIX - CACHE_ARGS - CACHE_OPTIONS ``CACHE_GCS_BUCKET`` is required and is the name of the bucket to use. The bucket must already exist. The following entries in CACHE_OPTIONS are recognised, any others are passed to ``google.cloud.storage.Client`` as ``**kwargs`` *delete_expired_objects_on_read* If ``True``, a read that finds a stale object deletes it before returning a response. This slows down responses. Defaults to ``False``. *anonymous* If ``True``, use anonymous credentials. Useful for testing. Defaults to ``False``. .. note:: Cache keys must be valid GCS object names, that is a sequence of Unicode characters whose UTF-8 encoding is at most 1024 bytes long. .. note:: Expired cache objects are not purged automatically. Unless *delete_expired_objects_on_read* is enabled, stale objects have to be deleted out of band, for example with a bucket lifecycle rule:: {"rule": [{"action": {"type": "Delete"}, "condition": {"daysSinceCustomTime": 0}}]} See `Object Lifecycle Management `_. .. versionadded:: 1.10.1 .. _uwsgicache: UWSGICache `````````` Set ``CACHE_TYPE`` to ``flask_caching.contrib.uwsgicache.UWSGICache`` to use this type. You also have to set ``CACHE_UWSGI_NAME`` to the cache name you set in your uWSGI configuration. Relevant configuration values - CACHE_DEFAULT_TIMEOUT - CACHE_IGNORE_ERRORS - CACHE_UWSGI_NAME .. note:: This backend cannot be used when running under PyPy, because the uWSGI API implementation for PyPy is lacking the needed functionality. .. versionchanged:: 1.10.0 Moved to the user contributed backends. pallets-eco-flask-caching-93fd601/docs/custom-backends.rst000066400000000000000000000050731524661224300235260ustar00rootroot00000000000000.. _custom-cache-backends: Custom Cache Backends --------------------- You are able to easily add your own custom cache backends by exposing a function that can instantiate and return a cache object. ``CACHE_TYPE`` will be the import string to your custom cache type. If not a subclass of :class:`.BaseCache`, Flask-Caching will call it with three arguments: * ``app``, the Flask application object the cache is being initialized for * ``args``, the value of the CACHE_ARGS configuration option * ``kwargs``, the value of the CACHE_OPTIONS configuration option .. note:: ``args`` and ``kwargs`` are not expanded when instantiating the cache object, i.e. they are not passed in as ``*args`` and ``**kwargs``, but they are the exact value of the CACHE_ARGS and CACHE_OPTIONS configuration options (CACHE_ARGS, however, is converted to a list). Your custom cache should, however, subclass the :class:`.BaseCache` class so it provides all the necessary methods to be usable. .. versionchanged:: 1.9.1 If your custom cache type *is* a subclass of :class:`.BaseCache`, Flask-Caching will, instead of directly instantiating the class, call its ``factory`` class method with the same args as listed above. Unless overridden, ``BaseCache.factory`` simply instantiates the object without passing any arguments to it. Built-in cache classes have overridden this to mimic the old, function based cache isntantiation, so if you subclassed something that is not :class:`.BaseCache`, you may want to consult the source code to see if your class is still compatible. An example implementation:: #: the_app/custom.py class RedisCache(BaseCache): def __init__(self, servers, default_timeout=500): pass @classmethod def factory(cls, app, args, kwargs): args.append(app.config['REDIS_SERVERS']) return cls(*args, **kwargs) With this example, your ``CACHE_TYPE`` might be ``the_app.custom.RedisCache`` CACHE_TYPE doesn’t have to directly point to a cache class, though. An example PylibMC cache implementation to change binary setting and provide username/password if SASL is enabled on the library:: #: the_app/custom.py def pylibmccache(app, config, args, kwargs): return pylibmc.Client(servers=config['CACHE_MEMCACHED_SERVERS'], username=config['CACHE_MEMCACHED_USERNAME'], password=config['CACHE_MEMCACHED_PASSWORD'], binary=True) With this example, your ``CACHE_TYPE`` might be ``the_app.custom.pylibmccache`` pallets-eco-flask-caching-93fd601/docs/index.rst000066400000000000000000000025241524661224300215510ustar00rootroot00000000000000Flask-Caching ============= .. module:: flask_caching :noindex: Flask-Caching is an extension to `Flask`_ that adds caching support for various backends to any Flask application. By running on top of `cachelib`_ it supports all of `werkzeug`_'s original caching backends through a uniformed API. It is also possible to develop your own caching backend by subclassing :class:`.BaseCache` class. .. code-block:: python from flask import Flask from flask_caching import Cache app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "SimpleCache"}) @app.route("/") @cache.cached(timeout=50) def index(): return "This response is cached for 50 seconds" Installation ------------ Install the extension with the following command:: $ pip install Flask-Caching or alternatively with uv:: $ uv add Flask-Caching Flask-Caching 2.5.0 supports Python 3.11+. Content ------- .. toctree:: :maxdepth: 2 quickstart usage configuration backends contrib-backends serializers custom-backends API --- .. toctree:: :maxdepth: 2 api Additional Information ---------------------- .. toctree:: :maxdepth: 2 changelog license * :ref:`search` .. _Flask: http://flask.pocoo.org/ .. _werkzeug: http://werkzeug.pocoo.org/ .. _cachelib: https://github.com/pallets/cachelib pallets-eco-flask-caching-93fd601/docs/license.rst000066400000000000000000000001031524661224300220530ustar00rootroot00000000000000License ======= .. literalinclude:: ../LICENSE :language: text pallets-eco-flask-caching-93fd601/docs/make.bat000066400000000000000000000171021524661224300213130ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-Caching.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-Caching.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end pallets-eco-flask-caching-93fd601/docs/quickstart.rst000066400000000000000000000044631524661224300226400ustar00rootroot00000000000000.. currentmodule:: flask_caching Quickstart ========== Cache is managed through a ``Cache`` instance:: from flask import Flask from flask_caching import Cache config = { "DEBUG": True, # some Flask specific configs "CACHE_TYPE": "SimpleCache", # Flask-Caching related configs "CACHE_DEFAULT_TIMEOUT": 300 } app = Flask(__name__) # tell Flask to use the above defined config app.config.from_mapping(config) cache = Cache(app) The configuration can also be passed straight to the ``Cache`` instance, without going through ``app.config``:: app = Flask(__name__) cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'}) You may also set up your ``Cache`` instance later at configuration time using **init_app** method:: cache = Cache(config={'CACHE_TYPE': 'SimpleCache'}) app = Flask(__name__) cache.init_app(app) You may also provide an alternate configuration dictionary, useful if there will be multiple ``Cache`` instances each with a different backend:: #: Method A: During instantiation of class cache = Cache(config={'CACHE_TYPE': 'SimpleCache'}) #: Method B: During init_app call cache.init_app(app, config={'CACHE_TYPE': 'SimpleCache'}) .. versionadded:: 0.7 Multiple cache objects can be instantiated with different configuration values. .. warning:: ``CACHE_TYPE`` defaults to ``NullCache``, which does not cache anything. A warning is emitted if it is left unset. Set ``CACHE_NO_NULL_WARNING`` to ``True`` to silence it. The available types/caching backends are listed under :ref:`CACHE_TYPE `. Caching a View -------------- With the cache set up, the :meth:`~Cache.cached` decorator caches the return value of a view:: @app.route("/") @cache.cached(timeout=50) def index(): return "This response is cached for 50 seconds" Caching Data ------------ Values can also be stored and read back directly. The proxy methods need an application context:: with app.app_context(): cache.set("answer", 42) cache.get("answer") # 42 Next Steps ---------- * :doc:`usage` covers view functions, memoization and template fragments. * :doc:`configuration` describes the configuration * :doc:`backends` describes the built-in cache backends. pallets-eco-flask-caching-93fd601/docs/serializers.rst000066400000000000000000000112751524661224300230010ustar00rootroot00000000000000.. _serializers: .. currentmodule:: flask_caching Overriding the Default Serializer --------------------------------- Values are serialized by cachelib before they are handed to the backend. Every backend holds a cachelib ``serializer`` instance. By default they all use ``pickle``: - :ref:`SimpleCache ` uses ``SimpleSerializer`` - :ref:`FileSystemCache ` uses ``FileSystemSerializer`` - :ref:`RedisCache ` uses ``RedisSerializer`` - :ref:`UWSGICache ` uses ``UWSGISerializer`` :ref:`NullCache ` and the memcached backends do not use one. Set ``CACHE_SERIALIZER`` to replace it:: from cachelib.serializers import JSONSerializer cache = Cache(app, config={ "CACHE_TYPE": "SimpleCache", "CACHE_SERIALIZER": JSONSerializer, }) The serializer must subclass ``cachelib.serializers.BaseSerializer``. It can also be an already created instance of one. Anything else raises a ``ValueError`` when the extension is initialized. If a backend does not use a serializer, Flask-Caching will print a warning. The custom serializer must implement ``dump``, ``dumps``, ``load`` and ``loads``. The serializer is assigend to :attr:`Cache.cache.serializer`, for example:: cache.cache.serializer = JSONSerializer() Example: Caching Unpicklable Values ``````````````````````````````````` The usual reason to replace the serializer is a value that pickle refuses to handle. Responses from ``send_from_directory`` are one example: they wrap an open file stream, and pickling a stream raises ``TypeError``. The simplest fix is not a serializer at all. Calling :meth:`~flask.Response.freeze` reads the stream into memory and makes the response picklable:: @app.route("/download") @cache.cached() def download(): response = send_from_directory(directory, filename) response.freeze() return response If the problem is not confined to one view, handle it in the serializer instead. Wrap the stream in a picklable object and fall back to it when ``pickle.dumps`` fails:: import io import pickle import typing as _t from os.path import basename from os.path import dirname from flask import Flask from flask import send_from_directory from flask_caching import Cache from cachelib.serializers import SimpleSerializer app = Flask(__name__) class _PicklableStream: """ Picklable wrapper for file like objects. """ def __init__(self, data: bytes): self._data = data self._buf = io.BytesIO(data) def read(self, size: int = -1) -> bytes: return self._buf.read(size) def __getstate__(self): return {"data": self._data} def __setstate__(self, state): self.__init__(state["data"]) class CustomSerializer(SimpleSerializer): """ Custom serializer that handles pickling files streams. Read the file stream into memory and store it as bytes. """ def _materialize_streams(self, value, _seen=None): _seen = _seen if _seen is not None else set() if id(value) in _seen: return value _seen.add(id(value)) if hasattr(value, "read") and callable(value.read): return _PicklableStream(value.read()) if hasattr(value, "__dict__"): for attr, attr_value in vars(value).items(): if hasattr(attr_value, "read") and callable(attr_value.read): setattr(value, attr, _PicklableStream(attr_value.read())) else: self._materialize_streams(attr_value, _seen) return value def dumps( self, value: _t.Any, protocol: int = pickle.HIGHEST_PROTOCOL ) -> bytes | None: try: serialized = pickle.dumps(value, protocol) except TypeError: materialized = self._materialize_streams(value) return pickle.dumps(materialized, protocol) except (pickle.PickleError, pickle.PicklingError) as e: self._warn(e) return None return serialized serializer_cache = Cache(app, config={ "CACHE_TYPE": "SimpleCache", "CACHE_SERIALIZER": CustomSerializer, }) @app.route("/serializer-override") @serializer_cache.cached() def root(): return send_from_directory(dirname(__file__), basename(__file__)) Reading the stream loads the whole file into memory, both when caching and when serving from the cache. Either approach trades memory for the cache hit. .. note:: Adapted from a suggestion in :issue:`167`. pallets-eco-flask-caching-93fd601/docs/usage.rst000066400000000000000000000562321524661224300215530ustar00rootroot00000000000000.. currentmodule:: flask_caching Usage ===== Caching View Functions ---------------------- To cache view functions you will use the :meth:`~Cache.cached` decorator. This decorator will use request.path by default for the cache_key:: @app.route("/") @cache.cached(timeout=50) def index(): return render_template('index.html') The cached decorator has another optional argument called ``unless``. This argument accepts a callable that returns True or False. If ``unless`` returns ``True`` then it will bypass the caching mechanism entirely. To dynamically determine the timeout within the view, you can return `CachedResponse`, a subclass of `flask.Response`:: @app.route("/") @cache.cached() def index(): return CachedResponse( response=make_response(render_template('index.html')), timeout=50, ) .. warning:: When using ``cached`` on a view, take care to put it between Flask's ``@route`` decorator and your function definition. Example:: @app.route('/') @cache.cached(timeout=50) def index(): return 'Cached for 50s' If you reverse both decorators, what will be cached is the result of ``@route`` decorator, and not the result of your view function. Deleting Cached Views ````````````````````` When you want to remove the value of a cached view you can use :meth:`~Cache.delete_cached` instead of :meth:`~Cache.delete`. :meth:`~Cache.delete_cached`. builds the key of the decorated view and deletes the cache in one go:: @app.route("/user/") @cache.cached(timeout=50) def user(name): return render_template("user.html", name=name) cache.delete_cached(user, "/user/Fred") Outside of a request context the view doesn't know which request was used to build the cache key. So to make it work outside of a request context you have to pass the ``path``. You can also use the view function and the named arguments of the view in which case the path is built using ``url_for()``:: cache.delete_cached(user, name="bob") When you cache views with ``query_string=True`` you also have to pass the query string/args because otherwise the cache key cannot be built:: @app.route("/works") @cache.cached(timeout=50, query_string=True) def works(): return do_search(request.args) cache.delete_cached(works, "/works", "limit=15&mock=true") You can use either pass the query string as a string, a mapping or an iterable of ``(key, value)`` pairs:: cache.delete_cached(works, "/works", {"limit": 15, "mock": "true"}) Additionally, ``path`` and ``query_args`` are also supported by the views ``make_cache_key()``:: key = works.make_cache_key(path="/works", query_args={"limit": 15}) .. note:: Inside a request context the view arguments are used as-is, so a view argument named ``path`` or ``query_args`` is not mistaken for the arguments above. Outside of a request context the two cannot be told apart, so such a view has to build its key from within the request context. Caching Pluggable View Classes ------------------------------ Flask's pluggable view classes are also supported. To cache them, use the same :meth:`~Cache.cached` decorator on the ``dispatch_request`` method:: from flask.views import View class MyView(View): @cache.cached(timeout=50) def dispatch_request(self): return 'Cached for 50s' Caching Other Functions ----------------------- Using the same ``@cached`` decorator you are able to cache the result of other non-view related functions. The only stipulation is that you replace the ``key_prefix``, otherwise it will use the request.path cache_key. Keys control what should be fetched from the cache. If, for example, a key does not exist in the cache, a new key-value entry will be created in the cache. Otherwise the value (i.e. the cached result) of the key will be returned:: @cache.cached(timeout=50, key_prefix='all_comments') def get_all_comments(): comments = do_serious_dbio() return [x.author for x in comments] cached_comments = get_all_comments() Make Custom `Cache Key` ----------------------- Sometimes you want to define your cache key for each route. Using the same ``@cached`` decorator you are able to specify how this key is generated. This might be useful when the key for cache should not be just the default key_prefix, but has to be derived from other parameters in a request. An example usecase would be for caching POST routes, where the cache key should be derived from the data in that request, rather than just the route/view itself. ``make_cache_key`` can be used to specify such a function. The function should return a string which should act like the key to the required value that is being cached:: def make_key(): """A function which is called to derive the key for a computed value. The key in this case is the concat value of all the json request parameters. Other strategy could to use any hashing function. :returns: unique string for which the value should be cached. """ user_data = request.get_json() return ",".join([f"{key}={value}" for key, value in user_data.items()]) @app.route("/hello", methods=["POST"]) @cache.cached(timeout=60, make_cache_key=make_key) def some_func(): .... Caching Routes With Multiple HTTP Methods ----------------------------------------- The default cache key is built from ``request.path`` alone, so a route that accepts more than one method serves all of them from the same entry:: @app.route("/foo", methods=["GET", "POST"]) @cache.cached(timeout=50) def foo(): return f"body for {request.method}" Whichever method is requested first is stored under ``view//foo`` and returned for the others until the entry expires. Pass a callable ``key_prefix`` to put the method into the key:: from flask import request def method_key(): return f"view/{request.method}{request.path}" @app.route("/foo", methods=["GET", "POST"]) @cache.cached(timeout=50, key_prefix=method_key) def foo(): return f"body for {request.method}" Each method now has its own entry, ``view/GET/foo`` and ``view/POST/foo``. The callable takes no arguments and is called inside the request context, so it can read anything from ``request``. It replaces the whole prefix, ``%s`` is not substituted. This also works with ``query_string=True``:: @app.route("/works", methods=["GET", "POST"]) @cache.cached(timeout=50, query_string=True, key_prefix=method_key) def works(): return do_search(request.args) When the key depends on the request body rather than the method use ``make_cache_key`` instead. See `Make Custom Cache Key`_. .. note:: Flask adds ``HEAD`` to every route that allows ``GET`` and strips the body from ``HEAD`` responses itself, so both methods can share one entry. Map ``HEAD`` onto ``GET`` to avoid caching the same body twice:: def method_key(): method = "GET" if request.method == "HEAD" else request.method return f"view/{method}{request.path}" However, only do this when the view builds the same response for both methods. .. warning:: A callable ``key_prefix`` replaces the ``path`` argument of :meth:`~Cache.delete_cached` so you have to delete the entry either from a request context:: with app.test_request_context("/foo", method="POST"): cache.delete_cached(foo) or pass the key to :meth:`~Cache.delete` yourself:: cache.delete("view/POST/foo") With ``query_string=True`` only the path comes from the callable, the ``query_args`` argument still applies:: with app.test_request_context("/works"): cache.delete_cached(works, query_args={"limit": 15}) Memoization ----------- See :meth:`~Cache.memoize` In memoization, the functions arguments are also included into the cache_key. .. note:: With functions that do not receive arguments, :meth:`~Cache.cached` and :meth:`~Cache.memoize` are effectively the same. Memoize is also designed for methods, since it will take into account the identity of the ``self`` or ``cls`` argument as part of the cache key. By default this identity is derived from ``repr(obj)`` (or ``obj.__caching_id__()`` if the object provides one). It is **not** based on Python's built-in :func:`id`, so two distinct instances that share the same ``repr`` (for example two ORM objects loaded for the same row) will share a memoize cache entry. The theory behind memoization is that if you have a function you need to call several times in one request, it would only be calculated the first time that function is called with those arguments. For example, an sqlalchemy object that determines if a user has a role. You might need to call this function many times during a single request. To keep from hitting the database every time this information is needed you might do something like the following:: class Person(db.Model): @cache.memoize(50) def has_membership(self, role_id): return Group.query.filter_by(user=self, role_id=role_id).count() >= 1 .. warning:: On a ``staticmethod`` or a ``classmethod``, ``@staticmethod`` or ``@classmethod`` the route decorator must be applied at the top of the decorator stack (visually first, logically last) following the same logic as ``@route`` and ``@cached`` above:: class Person(db.Model): @staticmethod @cache.memoize(50) def calc_budget(): return do_serious_dbio() .. warning:: Using mutable objects (classes, etc) as part of the cache key can become tricky. It is suggested to not pass in an object instance into a memoized function. However, the memoize does perform a repr() on the passed in arguments so that if the object has a __repr__ function that returns a uniquely identifying string for that object, that will be used as part of the cache key. For example, an sqlalchemy person object that returns the database id as part of the unique identifier:: class Person(db.Model): def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self.id) Deleting Memoize Cache `````````````````````` .. versionadded:: 0.2 You might need to delete the cache on a per-function basis. Using the above example, lets say you change the user's permissions and assign them to a role, but now you need to re-calculate if they have certain memberships or not. You can do this with the :meth:`~Cache.delete_memoized` function:: cache.delete_memoized(user_has_membership) .. note:: If only the function name is given as parameter, all the memoized versions of it will be invalidated. However, you can delete specific cache by providing the same parameter values as when caching. In following example only the ``user``-role cache is deleted: .. code-block:: python user_has_membership('demo', 'admin') user_has_membership('demo', 'user') cache.delete_memoized(user_has_membership, 'demo', 'user') .. warning:: If a classmethod is memoized, you must provide the ``class`` as the first ``*args`` argument. .. code-block:: python class Foobar(object): @classmethod @cache.memoize(5) def big_foo(cls, a, b): return a + b + random.randrange(0, 100000) cache.delete_memoized(Foobar.big_foo, Foobar, 5, 2) Memoized methods are stored per instance, so how much is deleted depends on whether the method is reached through an instance or through the class: .. code-block:: python class Adder(object): @cache.memoize(5) def add(self, b): return b + random.random() adder1 = Adder() adder2 = Adder() # only the calls made on adder1, adder2 keeps its cache cache.delete_memoized(adder1.add) # every instance cache.delete_memoized(Adder.add) # only ``adder1.add(3)`` cache.delete_memoized(adder1.add, 3) Instances are told apart by their ``repr()``, which can be overridden by defining a ``__caching_id__`` method, for example to key on a user id. .. warning:: A method reached through the class does not know which instance to delete the cache for, so the instance has to be passed as the first ``*args`` argument, the same way a ``class`` is passed for a ``classmethod``: .. code-block:: python cache.delete_memoized(Adder.add, adder1, 3) .. note:: The :meth:`~Cache.delete_memoized` attribute that :meth:`~Cache.memoize` puts on the decorated function takes no arguments and always clears every instance, even when it is called through one: .. code-block:: python adder1.add.delete_memoized() # same as cache.delete_memoized(Adder.add) Decorator Options ----------------- :meth:`~Cache.cached` and :meth:`~Cache.memoize` share most of their optional arguments. ``query_string`` and ``response_hit_indication`` are accepted by :meth:`~Cache.cached` only, ``make_name`` and ``args_to_ignore`` by :meth:`~Cache.memoize` only. unless `````` A callable that bypasses the cache entirely while it returns ``True``. The decorated function runs and its result is returned without the cache being read or written:: @app.route("/") @cache.cached(timeout=50, unless=lambda: current_user.is_authenticated) def index(): return render_template('index.html') If the callable accepts arguments it is called with the decorated function followed by the call's own arguments, otherwise it is called with none. forced_update ````````````` A callable that refreshes the cached value while it returns ``True``, even if the entry has not expired yet. Useful for background renewal:: @app.route("/") @cache.cached(timeout=50, forced_update=lambda: feature_flags["fresh"]) def index(): return render_template('index.html') If the callable accepts arguments it is called with the call's own arguments, otherwise it is called with none. Unlike ``unless``, the result is still written to the cache. is_stale ```````` .. versionadded:: 2.5.0 A callable that accepts the cached value as argument and checks whether the value is stale. If it returns ``True`` the cached value will be recomputed. Unlike ``forced_update`` it runs only on a cache hit:: def is_outdated(cached_obj): return get_last_data_updated() > cached_obj.last_update @cache.memoize(timeout=86400, is_stale=is_outdated) def build_report(user_id): return Report(user_id) If the callable accepts more than one argument, the calls own arguments are passed after the cached value:: def is_stale(report, user_id): return report.generated_at < last_import(user_id) @cache.memoize(timeout=86400, is_stale=is_stale) def build_report(user_id): return Report(user_id) ``forced_update`` and ``is_stale`` can be combined. ``forced_update`` is checked first and skips the cache read entirely. response_filter ``````````````` A callable invoked with the return value after the decorated function has run. If it returns ``False`` the value is not stored. Use it to keep failures out of the cache:: def only_success(response): return response.status_code == 200 @app.route("/") @cache.cached(timeout=50, response_filter=only_success) def index(): return render_template('index.html') .. versionchanged:: 2.5.0 An ``HTTPException`` raised by a view for example through :func:`flask.abort` is cached like a returned response and re-raised on a cache hit, so :meth:`~flask.Flask.errorhandler` functions still run. The filter is called with the exception's response. .. warning:: A view that aborts because of a server problem (i.e. status code ``503``) keeps returning that error until the entry expires. Use ``response_filter`` to keep such a status code out of the cache. In case a view returns just a plain string (has no ``status_code``), use a default value for the response. For example:: def not_server_error(response): return getattr(response, "status_code", 200) < 500 @app.route("/article/") @cache.cached(timeout=50, response_filter=not_server_error) def article(slug): if not backend.healthy(): abort(503) # raised again on every request article = load(slug) if article is None: abort(404) # cached and re-raised for 50 seconds return render_template("article.html", article=article) cache_none `````````` A cached ``None`` is indistinguishable from a cache miss, so by default the decorated function runs again on every call. Set ``cache_none`` to ``True`` to store ``None`` and use an extra key existence check to tell the two apart. .. warning:: This adds a second round trip to the backend, and can still return ``None`` wrongly if a concurrent call writes the key between the two calls. Returning a sentinel value instead is usually the better option. .. _source-check: source_check ```````````` Include the decorated function's source code in the cache key, so that editing the body invalidates values cached by the previous version even when the arguments are unchanged:: @cache.memoize(timeout=50, source_check=True) def add(a, b): return a + b Defaults to the ``CACHE_SOURCE_CHECK`` configuration value, which is ``False``. This is meant for development, where a stale value from an older revision of a function is confusing. It calls :func:`inspect.getsource` on every call, so leave it off in production. .. _hash-method: hash_method ``````````` The hash constructor used when building cache keys. Defaults to the ``CACHE_HASH_METHOD`` configuration value, which is :func:`hashlib.sha256`. Set it application wide:: app.config["CACHE_HASH_METHOD"] = hashlib.sha512 or override it for a single decorator:: @cache.memoize(timeout=50, hash_method=hashlib.sha512) def add(a, b): return a + b .. warning:: The cache key is derived from this hash, so changing it makes every existing cached entry unreachable. The stale entries are not deleted; they remain in the backend until they expire. Do not confuse this with ``CACHE_FILE_HASH_METHOD``, which the FileSystemCache backend uses to name the files it writes. The two are independent. query_string ```````````` :meth:`~Cache.cached` only. Build the cache key from the request's query string instead of ``key_prefix``. The arguments are sorted before hashing, so ``?limit=10&offset=20`` and ``?offset=20&limit=10`` share one entry:: @app.route("/search") @cache.cached(timeout=50, query_string=True) def search(): return do_search(request.args) Deleting such an entry needs the query string as well, see `Deleting cached views`_. response_hit_indication ``````````````````````` :meth:`~Cache.cached` only. When ``True``, responses that were served from the cache carry a ``hit_cache`` header. Responses produced by running the view do not:: @app.route("/") @cache.cached(timeout=50, response_hit_indication=True) def index(): return render_template('index.html') .. versionadded:: 2.3.0 make_name ````````` :meth:`~Cache.memoize` only. A callable that receives the name of the decorated function and returns the name to use in the cache key. Without it the function name is used. args_to_ignore `````````````` :meth:`~Cache.memoize` only. Names of arguments to leave out of the cache key, so that calls differing only in those arguments share an entry:: @cache.memoize(timeout=50, args_to_ignore=["session"]) def get_user(session, user_id): return session.query(User).get(user_id) .. versionadded:: 1.10 Caching Jinja2 Snippets ----------------------- Usage:: {% cache [timeout [,[key1, [key2, ...]]]] %} ... {% endcache %} By default, the value of "path to template file" + "block start line" is used as the cache key. Also, the key name can be set manually. Keys are concatenated together into a single string, that can be used to avoid the same block evaluating in different templates. Set the timeout to ``None`` for no timeout, but with custom keys:: {% cache None, "key" %} ... {% endcache %} Set timeout to ``del`` to delete cached value:: {% cache 'del', key1 %} ... {% endcache %} If keys are provided, you may easily generate the template fragment key and delete it from outside of the template context:: from flask_caching import make_template_fragment_key key = make_template_fragment_key("key1", vary_on=["key2", "key3"]) cache.delete(key) Considering we have ``render_form_field`` and ``render_submit`` macros:: {% cache 60*5 %}
{% render_form_field(form.username) %} {% render_submit() %}
{% endcache %} Clearing Cache -------------- See :meth:`~Cache.clear`. To delete the entry of a single view see `Deleting cached views`_. Here's an example script to empty your application's cache: .. code-block:: python from flask_caching import Cache from yourapp import app, your_cache_config cache = Cache() def main(): cache.init_app(app, config=your_cache_config) with app.app_context(): cache.clear() if __name__ == '__main__': main() .. warning:: Some backend implementations do not support completely clearing the cache. Also, if you're not using a key prefix, some implementations (e.g. Redis) will flush the whole database. Make sure you're not storing any other data in your caching database. Explicitly Caching Data ----------------------- Data can be cached explicitly by using the proxy methods like :meth:`Cache.set`, and :meth:`Cache.get` directly. There are many other proxy methods available via the :class:`Cache` class. For example: .. code-block:: python @app.route("/html") @app.route("/html/") def html(foo=None): if foo is not None: cache.set("foo", foo) bar = cache.get("foo") return render_template_string( "foo cache: {{bar}}", bar=bar ) Subclassing ``Cache`` --------------------- The proxy methods are the extension's own entry point to the backend, so overriding them in a :class:`Cache` subclass also affects the caching done by :meth:`~Cache.cached` and :meth:`~Cache.memoize`. This makes it possible to add tracing, metrics or logging in one place and have it cover both explicit calls and the decorators: .. code-block:: python class InstrumentedCache(Cache): def get(self, *args, **kwargs): with tracer.trace("cache.get"): return super().get(*args, **kwargs) def set(self, *args, **kwargs): with tracer.trace("cache.set"): return super().set(*args, **kwargs) cache = InstrumentedCache(app, config={"CACHE_TYPE": "SimpleCache"}) Note that :meth:`~Cache.memoize` also uses :meth:`~Cache.get_many` and :meth:`~Cache.set_many` for its internal version keys, so an override will see that bookkeeping traffic as well. A cache miss makes two such round trips: one to read the version before building the key, and one after the entry is written to update the version keys expiry. To reach backend specific functionality that :class:`Cache` does not proxy, use the :attr:`Cache.cache` property instead of subclassing. pallets-eco-flask-caching-93fd601/examples/000077500000000000000000000000001524661224300205735ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/examples/hello.cfg000066400000000000000000000001641524661224300223600ustar00rootroot00000000000000 SECRET_KEY = '\xfb\x12\xdf\xa1@i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16' DEBUG = True CACHE_TYPE = 'SimpleCache' pallets-eco-flask-caching-93fd601/examples/hello.py000066400000000000000000000031661524661224300222560ustar00rootroot00000000000000import random from datetime import datetime from flask import Flask from flask import jsonify from flask import render_template_string from flask_caching import Cache app = Flask(__name__) app.config.from_pyfile("hello.cfg") cache = Cache(app) #: This is an example of a cached view @app.route("/api/now") @cache.cached(50) def current_time(): return str(datetime.now()) #: This is an example of a cached function @cache.cached(key_prefix="binary") def random_binary(): return [random.randrange(0, 2) for i in range(500)] @app.route("/api/get/binary") def get_binary(): return jsonify({"data": random_binary()}) #: This is an example of a memoized function @cache.memoize(60) def _add(a, b): return a + b + random.randrange(0, 1000) @cache.memoize(60) def _sub(a, b): return a - b - random.randrange(0, 1000) @app.route("/api/add//") def add(a, b): return str(_add(a, b)) @app.route("/api/sub//") def sub(a, b): return str(_sub(a, b)) @app.route("/api/cache/delete") def delete_cache(): cache.delete_memoized("_add", "_sub") return "OK" @app.route("/html") @app.route("/html/") def html(foo=None): if foo is not None: cache.set("foo", foo) return render_template_string( "foo cache: {{foo}}", foo=cache.get("foo") ) @app.route("/template") def template(): return render_template_string( """foo cache: {% cache 60, "random" %} {{ range(1, 42) | random }} {% endcache %} """ ) if __name__ == "__main__": app.run() pallets-eco-flask-caching-93fd601/pyproject.toml000066400000000000000000000132641524661224300216770ustar00rootroot00000000000000[project] name = "Flask-Caching" version = "2.5.1" description = "Adds caching support to Flask applications." readme = "README.md" license = "BSD-3-Clause" license-files = ["LICENSE"] authors = [{ name = "Peter Justin", email="peter.justin@outlook.com" }] maintainers = [{ name = "Pallets Ecosystem", email = "contact@palletsprojects.com" }] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Programming Language :: Python', ] requires-python = ">=3.11" dependencies = [ "flask>=3.0", "cachelib>=0.17.0", ] [project.urls] Documentation = "https://flask-caching.readthedocs.io" Changes = "https://github.com/pallets-eco/flask-caching/releases/" Source = "https://github.com/pallets-eco/flask-caching" Chat = "https://discord.gg/pallets" [dependency-groups] dev = [ "ruff", {include-group = "tox"}, {include-group = "pre-commit"}, {include-group = "docs"}, {include-group = "tests"}, {include-group = "typing"}, ] tox = [ "tox>=4.39", "tox-uv", ] pre-commit = [ "pre-commit", "pre-commit-uv", ] docs = [ "pallets-sphinx-themes", "sphinx", "sphinx-issues", "sphinx-tabs", "sphinx-intl>=2.3.1", "sphinxcontrib-log-cabinet", ] docs-auto = [ "sphinx-autobuild", ] tests = [ "flask[async]", "pytest", "pytest-cov", "pytest-asyncio", "pytest-xprocess", "pylibmc", "redis", "asgiref", ] typing = [ "mypy", "pyright", "pytest", "types-redis>=4.6.0.20241004", "types-uwsgi>=2.0.0.20260408", ] gha-update = [ "gha-update ; python_full_version >= '3.12'", ] [build-system] requires = ["flit_core>=3.11,<5"] build-backend = "flit_core.buildapi" [tool.flit.module] name = "flask_caching" [tool.flit.sdist] include = [ "docs/", "examples/", "tests/", "CHANGES.rst", "uv.lock" ] exclude = [ "docs/_build/", ] [tool.uv] default-groups = ["dev", "pre-commit", "tests", "typing"] [tool.ruff] src = ["src"] fix = true show-fixes = true output-format = "full" [tool.ruff.lint] select = [ "B", # flake8-bugbear "E", # pycodestyle error "F", # pyflakes "I", # isort "UP", # pyupgrade "W", # pycodestyle warning ] ignore = [ "UP007", # Use X | Y for type annotations; doesn't work with some of our (stringy) type definitions - should aim to resolve this ] [tool.ruff.lint.isort] force-single-line = true order-by-type = false [tool.coverage.run] branch = true source = ["flask_caching", "tests"] omit = ["src/flask_caching/contrib/*"] [tool.coverage.paths] source = ["src", "*/site-packages"] [tool.coverage.report] exclude_also = [ "if t.TYPE_CHECKING", "raise NotImplementedError", ": \\.{3}", ] [tool.pytest.ini_options] testpaths = ["tests"] filterwarnings = [ "error", "default::pytest.PytestUnraisableExceptionWarning", ] [tool.mypy] python_version = "3.11" files = ["src/flask_caching/", "tests/typing/"] warn_unused_configs = true strict = true [[tool.mypy.overrides]] module = [ "pylibmc", "google.*", ] ignore_missing_imports = true [tool.pyright] pythonVersion = "3.11" include = ["src", "tests/typing"] typeCheckingMode = "standard" [tool.tox] env_list = [ "py3.14", "py3.13", "py3.12", "py3.11", "style", "typing", "docs", "docs-auto", ] [tool.tox.env_run_base] description = "pytest on latest dependency versions" runner = "uv-venv-lock-runner" package = "wheel" wheel_build_env = ".pkg" constrain_package_deps = true use_frozen_constraints = true dependency_groups = ["tests"] pass_env = ["CI"] env_tmp_dir = "{toxworkdir}/tmp/{envname}" commands = [[ "pytest", "-v", "--capture=tee-sys", "--tb=short", "--basetemp={env_tmp_dir}", {replace = "posargs", default = [], extend = true}, ]] [tool.tox.env.style] description = "run all pre-commit hooks on all files" dependency_groups = ["pre-commit"] skip_install = true commands = [["pre-commit", "run", "--all-files"]] [tool.tox.env.typing] description = "run static type checkers" dependency_groups = ["typing"] commands = [ ["mypy"], ] [tool.tox.docs] docs_dir = "{tox_root}{/}docs" build_dir = "{[tool.tox.docs]docs_dir}{/}_build" sphinx_args = ["--show-traceback", "--fail-on-warning", "--keep-going", "--fresh-env"] [tool.tox.env.docs] description = "build docs" dependency_groups = ["docs"] commands = [[ "sphinx-build", { replace = "ref", of = ["tool", "tox", "docs", "sphinx_args"], extend = true }, { replace = "posargs", default = [], extend = true }, "--builder", "dirhtml", "{[tool.tox.docs]docs_dir}", "{[tool.tox.docs]build_dir}{/}dirhtml", ]] [tool.tox.env.docs-auto] description = "continuously rebuild docs and start a local server" dependency_groups = ["docs", "docs-auto"] package = "editable" commands = [[ "sphinx-autobuild", { replace = "ref", of = ["tool", "tox", "docs", "sphinx_args"], extend = true }, { replace = "posargs", default = [], extend = true }, "--builder", "dirhtml", "--watch", "src", "{[tool.tox.docs]docs_dir}", "{[tool.tox.docs]build_dir}{/}live", ]] [tool.tox.env.update-actions] description = "update GitHub Actions pins" labels = ["update"] dependency_groups = ["gha-update"] skip_install = true commands = [["gha-update"]] [tool.tox.env.update-pre_commit] description = "update pre-commit pins" labels = ["update"] dependency_groups = ["pre-commit"] skip_install = true commands = [["pre-commit", "autoupdate", "--freeze", "-j4"]] [tool.tox.env.update-requirements] description = "update uv lock" labels = ["update"] dependency_groups = [] no_default_groups = true skip_install = true commands = [["uv", "lock", {replace = "posargs", default = ["-U"], extend = true}]] pallets-eco-flask-caching-93fd601/src/000077500000000000000000000000001524661224300175445ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/src/flask_caching/000077500000000000000000000000001524661224300223205ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/src/flask_caching/__init__.py000066400000000000000000001663031524661224300244420ustar00rootroot00000000000000""" flask_caching ~~~~~~~~~~~~~ Adds cache support to your application. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ import base64 import functools import hashlib import inspect import logging import uuid import warnings from collections import OrderedDict from collections.abc import Callable from collections.abc import Iterable from typing import Any from typing import cast from typing import Concatenate from typing import overload from typing import ParamSpec from typing import Protocol from typing import TypeAlias from typing import TypeVar from cachelib.serializers import BaseSerializer from flask import current_app from flask import Flask from flask import g from flask import request from flask import Response from flask import url_for from werkzeug.exceptions import HTTPException from werkzeug.utils import import_string from .backends.base import BaseCache from .backends.simplecache import SimpleCache from .signals import cache_memoize_hit as cache_memoize_hit from .signals import cache_memoize_miss as cache_memoize_miss from .signals import cache_view_hit as cache_view_hit from .signals import cache_view_miss as cache_view_miss from .utils import _QueryArgs from .utils import _Timeout from .utils import function_namespace from .utils import get_arg_default from .utils import get_arg_names from .utils import get_id from .utils import join_generator from .utils import make_template_fragment_key as make_template_fragment_key from .utils import normalize_timeout from .utils import query_args_as_pairs from .utils import wants_args from .utils import wants_extra_args logger = logging.getLogger(__name__) # The initial version timeout of a memoize version key. Will be overwritten on the first # write with the memoize value timeout VERSION_TIMEOUT = 0 SUPPORTED_HASH_FUNCTIONS = [ hashlib.sha1, hashlib.sha224, hashlib.sha256, hashlib.sha384, hashlib.sha512, hashlib.md5, ] P = ParamSpec("P") # The parameters left over after ``__get__`` binds the instance. P2 = ParamSpec("P2") R = TypeVar("R") T = TypeVar("T") # ``uncached`` is read-only on the bound protocols, so their instance and # return types are contravariant and covariant respectively. T_co = TypeVar("T_co", covariant=True) T_contra = TypeVar("T_contra", contravariant=True) class _BoundCachedFunction(Protocol[T_contra, P, T_co]): """The type of a :meth:`Cache.cached` method accessed on an instance.""" cache_timeout: _Timeout | None make_cache_key: Callable[..., str] @property def uncached(self) -> Callable[Concatenate[T_contra, P], T_co]: ... def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T_co: ... class _CachedFunction(Protocol[P, R]): """The type of the callable returned by :meth:`Cache.cached`.""" uncached: Callable[P, R] cache_timeout: _Timeout | None make_cache_key: Callable[..., str] def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ... # The decorator returns a function, so decorating a method keeps working # at runtime. Without ``__get__`` type checkers see a plain attribute # instead of a descriptor and never bind ``self``. @overload def __get__( self, instance: None, owner: type | None = None, / ) -> "_CachedFunction[P, R]": ... @overload def __get__( self: "_CachedFunction[Concatenate[T, P2], R]", instance: T, owner: type | None = None, /, ) -> _BoundCachedFunction[T, P2, R]: ... def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... class _BoundMemoizedFunction(Protocol[T_contra, P, T_co]): """The type of a :meth:`Cache.memoize` method accessed on an instance.""" cache_timeout: _Timeout | None make_cache_key: Callable[..., str] delete_memoized: Callable[[], None] @property def uncached(self) -> Callable[Concatenate[T_contra, P], T_co]: ... def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T_co: ... class _MemoizedFunction(Protocol[P, R]): """The type of the callable returned by :meth:`Cache.memoize`.""" uncached: Callable[P, R] cache_timeout: _Timeout | None make_cache_key: Callable[..., str] delete_memoized: Callable[[], None] def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ... @overload def __get__( self, instance: None, owner: type | None = None, / ) -> "_MemoizedFunction[P, R]": ... @overload def __get__( self: "_MemoizedFunction[Concatenate[T, P2], R]", instance: T, owner: type | None = None, /, ) -> _BoundMemoizedFunction[T, P2, R]: ... def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... # A memoized function, however it was reached: a plain function, a method # accessed on the class, or a method accessed on an instance. _AnyMemoizedFunction: TypeAlias = ( "_MemoizedFunction[..., Any] | _BoundMemoizedFunction[Any, ..., Any]" ) _AnyCachedFunction: TypeAlias = ( "_CachedFunction[..., Any] | _BoundCachedFunction[Any, ..., Any]" ) class CachedResponse(Response): """ views wraped by @cached can return this (which inherits from flask.Response) to override the cache TTL dynamically """ timeout: int | None = None def __init__(self, response: Response, timeout: _Timeout | None) -> None: # ``CachedResponse`` adopts the state of an existing Response in # place rather than calling ``Response.__init__``; copying # ``__dict__`` preserves headers, status and body without having # to round-trip the response through Werkzeug's constructor. self.__dict__ = response.__dict__ self.timeout = normalize_timeout(timeout) class Cache: """This class is used to control the cache objects.""" def __init__( self, app: Flask | None = None, with_jinja2_ext: bool = True, config: dict[str, Any] | None = None, ) -> None: if not (config is None or isinstance(config, dict)): raise ValueError("`config` must be an instance of dict or None") self.with_jinja2_ext = with_jinja2_ext self.config = config self.source_check = None self.hash_method: Callable[..., Any] | None = None self.serializer: BaseSerializer | None = None self.enable_signals = False if app is not None: self.init_app(app, config) def init_app(self, app: Flask, config: dict[str, Any] | None = None) -> None: """This is used to initialize cache with your app object""" if not (config is None or isinstance(config, dict)): raise ValueError("`config` must be an instance of dict or None") #: Ref PR #44. #: Do not set self.app in the case a single instance of the Cache #: object is being used for multiple app instances. #: Example use case would be Cache shipped as part of a blueprint #: or utility library. base_config = app.config.copy() if self.config: base_config.update(self.config) if config: base_config.update(config) config = base_config config.setdefault("CACHE_DEFAULT_TIMEOUT", 300) config.setdefault("CACHE_IGNORE_ERRORS", False) config.setdefault("CACHE_THRESHOLD", 500) config.setdefault("CACHE_KEY_PREFIX", "flask_cache_") config.setdefault("CACHE_MEMCACHED_SERVERS", None) config.setdefault("CACHE_DIR", None) config.setdefault("CACHE_FILE_HASH_METHOD", hashlib.sha256) config.setdefault("CACHE_HASH_METHOD", hashlib.sha256) config.setdefault("CACHE_OPTIONS", None) config.setdefault("CACHE_ARGS", []) config.setdefault("CACHE_TYPE", "NullCache") config.setdefault("CACHE_NO_NULL_WARNING", False) config.setdefault("CACHE_SOURCE_CHECK", False) config.setdefault("CACHE_ENABLE_SIGNALS", False) config.setdefault("CACHE_SERIALIZER", None) if config["CACHE_TYPE"] == "NullCache" and not config["CACHE_NO_NULL_WARNING"]: warnings.warn( "Flask-Caching: CACHE_TYPE is set to NullCache, " "caching is effectively disabled.", stacklevel=2, ) if config["CACHE_TYPE"] == "FileSystemCache" and config["CACHE_DIR"] is None: warnings.warn( f"Flask-Caching: CACHE_TYPE is set to {config['CACHE_TYPE']} but no " "CACHE_DIR is set.", stacklevel=2, ) self.source_check = config["CACHE_SOURCE_CHECK"] self.enable_signals = config["CACHE_ENABLE_SIGNALS"] # Validated here rather than lazily so that a bad hash method is # reported at startup instead of on the first cached call. if not callable(config["CACHE_HASH_METHOD"]): raise ValueError( "`CACHE_HASH_METHOD` must be a hash constructor, for example " f"`hashlib.sha256`, not {config['CACHE_HASH_METHOD']!r}" ) self.hash_method = config["CACHE_HASH_METHOD"] self.serializer = self._get_serializer(config["CACHE_SERIALIZER"]) if self.with_jinja2_ext: from .jinja2ext import CacheExtension from .jinja2ext import JINJA_CACHE_ATTR_NAME setattr(app.jinja_env, JINJA_CACHE_ATTR_NAME, self) app.jinja_env.add_extension(CacheExtension) self._set_cache(app, config) def _set_cache(self, app: Flask, config: dict[str, Any]) -> None: import_me = config["CACHE_TYPE"] if "." not in import_me: import_me = "flask_caching.backends." + import_me cache_factory = import_string(import_me) cache_args = config["CACHE_ARGS"][:] cache_options = { "default_timeout": config["CACHE_DEFAULT_TIMEOUT"], "ignore_delete_many_errors": config["CACHE_IGNORE_ERRORS"], } if isinstance(cache_factory, type) and issubclass(cache_factory, BaseCache): cache_factory = cache_factory.factory if config["CACHE_OPTIONS"]: cache_options.update(config["CACHE_OPTIONS"]) cache_options["default_timeout"] = normalize_timeout( cache_options["default_timeout"] ) # Backends that are not created from cachelib read the timeout from # config instead of cache_options. config["CACHE_DEFAULT_TIMEOUT"] = cache_options["default_timeout"] if not hasattr(app, "extensions"): app.extensions = {} app.extensions.setdefault("cache", {}) if import_me.find("cachelib") > -1: cache = cache_factory(*cache_args, **cache_options) else: cache = cache_factory(app, config, cache_args, cache_options) if self.serializer is not None: if hasattr(cache, "serializer"): cache.serializer = self.serializer # pyright: ignore[reportAttributeAccessIssue] else: warnings.warn( f"CACHE_SERIALIZER is set but {type(cache).__name__} does not use" "a serializer.", stacklevel=2, ) app.extensions["cache"][self] = cache self.app = app def _get_serializer(self, serializer: Any) -> BaseSerializer | None: """Returns the serializer instance for the caching backend. If None, it will use the default one. """ if serializer is None: return None if isinstance(serializer, type) and issubclass(serializer, BaseSerializer): try: return serializer() except TypeError as e: raise ValueError( f"Couldn't instantiate serializer: {serializer!r}!" ) from e if not isinstance(serializer, BaseSerializer): raise ValueError( "`CACHE_SERIALIZER` must be a `cachelib.serializers.BaseSerializer` " "subclass or instance!" ) return serializer def _get_hash_method( self, hash_method: Callable[..., Any] | None ) -> Callable[..., Any]: """Returns the hash method for the cache keys. Defaults to hashlib.sha256""" if hash_method is not None: return hash_method return self.hash_method or hashlib.sha256 def _get_source_check(self, source_check: bool | None) -> bool | None: """Resolve whether the function's source belongs in the cache key.""" if source_check is not None: return source_check return self.source_check def _call_fn(self, fn: Callable[..., Any], *args: Any, **kwargs: Any) -> Any: ensure_sync = getattr(self.app, "ensure_sync", None) if ensure_sync is not None: return ensure_sync(fn)(*args, **kwargs) return fn(*args, **kwargs) def _call_fn_or_exception( self, fn: Callable[..., Any], *args: Any, **kwargs: Any ) -> Any: """Returns an ``HTTPException`` instead of propagating it.""" try: return self._call_fn(fn, *args, **kwargs) except HTTPException as e: return e @property def cache(self) -> SimpleCache: """The backend instance the proxy methods delegate to. Use this to reach backend specific functionality that ``Cache`` does not proxy. Requires an application context. """ app = current_app or self.app return cast("SimpleCache", app.extensions["cache"][self]) def get(self, *args: Any, **kwargs: Any) -> Any: """Proxy function for internal cache object.""" return self.cache.get(*args, **kwargs) def has(self, *args: Any, **kwargs: Any) -> bool: """Proxy function for internal cache object.""" return self.cache.has(*args, **kwargs) def set(self, *args: Any, **kwargs: Any) -> bool | None: """Proxy function for internal cache object.""" return self.cache.set(*args, **kwargs) def add(self, *args: Any, **kwargs: Any) -> bool: """Proxy function for internal cache object.""" return self.cache.add(*args, **kwargs) def delete(self, *args: Any, **kwargs: Any) -> bool: """Proxy function for internal cache object.""" return self.cache.delete(*args, **kwargs) def delete_many(self, *args: Any, **kwargs: Any) -> list[str]: """Proxy function for internal cache object.""" return self.cache.delete_many(*args, **kwargs) def clear(self) -> bool: """Proxy function for internal cache object.""" return self.cache.clear() def get_many(self, *args: Any, **kwargs: Any) -> list[Any]: """Proxy function for internal cache object.""" return self.cache.get_many(*args, **kwargs) def set_many(self, *args: Any, **kwargs: Any) -> list[Any]: """Proxy function for internal cache object.""" return self.cache.set_many(*args, **kwargs) def get_dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: """Proxy function for internal cache object.""" return self.cache.get_dict(*args, **kwargs) def unlink(self, *args: Any, **kwargs: Any) -> list[str]: """Proxy function for internal cache object only support Redis """ unlink = getattr(self.cache, "unlink", None) if unlink is not None and callable(unlink): return cast("list[str]", unlink(*args, **kwargs)) return self.delete_many(*args, **kwargs) def cached( self, timeout: _Timeout | None = None, key_prefix: str | Callable[[], str] = "view/%s", unless: Callable[..., Any] | None = None, forced_update: Callable[..., Any] | None = None, is_stale: Callable[..., Any] | None = None, response_filter: Callable[..., Any] | None = None, query_string: bool = False, hash_method: Callable[..., Any] | None = None, cache_none: bool = False, make_cache_key: Callable[..., Any] | None = None, source_check: bool | None = None, response_hit_indication: bool | None = False, ) -> Callable[[Callable[P, R]], _CachedFunction[P, R]]: """Decorator. Use this to cache a function. By default the cache key is `view/request.path`. You are able to use this decorator with any function by changing the `key_prefix`. If the token `%s` is located within the `key_prefix` then it will replace that with `request.path` Example:: # An example view function @cache.cached(timeout=50) def big_foo(): return big_bar_calc() # An example misc function to cache. @cache.cached(key_prefix='MyCachedList') def get_list(): return [random.randrange(0, 1) for i in range(50000)] my_list = get_list() .. note:: You MUST have a request context to actually called any functions that are cached. .. versionadded:: 0.4 The returned decorated function now has three function attributes assigned to it. These attributes are readable/writable. **uncached** The original undecorated function **cache_timeout** The cache timeout value for this function. For a custom value to take affect, this must be set before the function is called. **make_cache_key** A function used in generating the cache_key used. readable and writable Outside of a request context, pass ``path`` to build the key for a given ``request.path`` and, for ``query_string=True``, ``query_args`` to build the key for a given query string:: key = view.make_cache_key( path="/works", query_args="limit=15&mock=true" ) cache.delete(key) ``query_args`` accepts a query string, a mapping or an iterable of ``(key, value)`` pairs. See :meth:`delete_cached` for the shorthand. :param timeout: Default None. If set to an integer, will cache for that amount of time. Unit of time is in seconds. A ``datetime.timedelta`` is also accepted and is rounded up to whole seconds. :param key_prefix: Default 'view/%(request.path)s'. Beginning key to . use for the cache key. `request.path` will be the actual request path, or in cases where the `make_cache_key`-function is called from other views it will be the expected URL for the view as generated by Flask's `url_for()`. .. versionadded:: 0.3.4 Can optionally be a callable which takes no arguments but returns a string that will be used as the cache_key. :param unless: Default None. Cache will *always* execute the caching facilities unless this callable is true. This will bypass the caching entirely. :param forced_update: Default None. If this callable is true, cache value will be updated regardless cache is expired or not. Useful for background renewal of cached functions. :param is_stale: Default None. Called on a cache hit with the cached value as its first argument. If it is true the cached value will be recomputed. If the callable accepts more than one argument, the calls own arguments are passed after the cached value. :param response_filter: Default None. If not None, the callable is invoked after the cached function evaluation, and is given one argument, the response content. If the callable returns False, the content will not be cached. Useful to prevent caching of code 500 responses. :param query_string: Default False. When True, the cache key used will be the result of hashing the ordered query string parameters. This avoids creating different caches for the same query just because the parameters were passed in a different order. See _make_cache_key_query_string() for more details. .. versionchanged:: 2.5.0 Include ``key_prefix`` when building query string cache keys. :param hash_method: Default None. If None will use the value set by ``CACHE_HASH_METHOD``, which defaults to ``hashlib.sha256``. The hash method used to generate the keys for cached results. :param cache_none: Default False. If set to True, add a key exists check when cache.get returns None. This will likely lead to wrongly returned None values in concurrent situations and is not recommended to use. :param make_cache_key: Default None. If set to a callable object, it will be called to generate the cache key :param source_check: Default None. If None will use the value set by CACHE_SOURCE_CHECK. If True, include the function's source code in the hash to avoid using cached values when the source code has changed and the input values remain the same. This ensures that the cache_key will be formed with the function's source code hash in addition to other parameters that may be included in the formation of the key. :param response_hit_indication: Default False. If True, it will add to response header field 'hit_cache' if used cache. .. versionchanged:: 2.5.0 A ``werkzeug.exceptions.HTTPException`` raised by the decorated function, for example through Flask's ``abort()``, is now cached like a returned response and re-raised on a cache hit. Use ``response_filter``, which is given the exception's response, to keep it out of the cache. """ def decorator(f: Callable[P, R]) -> _CachedFunction[P, R]: @functools.wraps(f) def decorated_function(*args: Any, **kwargs: Any) -> Any: #: Bypass the cache entirely. if self._bypass_cache(unless, f, *args, **kwargs): return self._call_fn(f, *args, **kwargs) try: if make_cache_key is not None and callable(make_cache_key): cache_key = make_cache_key(*args, **kwargs) else: cache_key = cached_fn.make_cache_key( *args, use_request=True, **kwargs ) if self._forced_update(forced_update, *args, **kwargs): rv = None found = False else: rv = self.get(cache_key) found = True # If the value returned by cache.get() is None, it # might be because the key is not found in the cache # or because the cached value is actually None if rv is None: # If we're sure we don't need to cache None values # (cache_none=False), don't bother checking for # key existence, as it can lead to false positives # if a concurrent call already cached the # key between steps. This would cause us to # return None when we shouldn't if not cache_none: found = False else: found = self.has(cache_key) if found and self._is_stale(is_stale, rv, *args, **kwargs): rv = None found = False except Exception: if self.app.debug: raise logger.exception("Exception possibly due to cache backend.") return self._call_fn(f, *args, **kwargs) if found and self.app.debug: logger.info(f"Cache used for key: {cache_key}") if response_hit_indication: g.flask_caching_hit_cache = found def apply_caching(response: Response) -> Response: if g.get("flask_caching_hit_cache"): response.headers["hit_cache"] = g.flask_caching_hit_cache return response after_request_funcs = self.app.after_request_funcs[None] if not any( getattr(func, "__name__", None) == "apply_caching" for func in after_request_funcs ): after_request_funcs.append(apply_caching) if self.enable_signals: signal = cache_view_hit if found else cache_view_miss signal.send( cache=self, cache_key=cache_key, args=args, kwargs=kwargs ) if found and isinstance(rv, HTTPException): raise rv if not found: rv = self._call_fn_or_exception(f, *args, **kwargs) if inspect.isgenerator(rv): rv = join_generator(rv) if response_filter is None or response_filter( rv.get_response() if isinstance(rv, HTTPException) else rv ): cache_timeout = normalize_timeout(cached_fn.cache_timeout) if isinstance(rv, CachedResponse): cache_timeout = rv.timeout or cache_timeout try: self.set( cache_key, rv, timeout=cache_timeout, ) except Exception: if self.app.debug: raise logger.exception("Exception possibly due to cache backend.") if isinstance(rv, HTTPException): raise rv return rv def default_make_cache_key(*args: Any, **kwargs: Any) -> str: # Convert non-keyword arguments (which is the way # `make_cache_key` expects them) to keyword arguments # (the way `url_for` expects them) argspec_args = get_arg_names(f) for arg_name, arg in zip(argspec_args, args, strict=False): kwargs[arg_name] = arg use_request = kwargs.pop("use_request", False) if use_request: # The remaining kwargs are the view arguments of the # current request. View arguments named ``path`` or # ``query_args`` must not be mistaken for the explicit # arguments of the same name. path = None query_args = None else: path = kwargs.pop("path", None) query_args = kwargs.pop("query_args", None) return _make_cache_key( args, kwargs, use_request=use_request, path=path, query_args=query_args, ) def _make_cache_key_query_string( path: str | None = None, query_args: _QueryArgs | None = None, ) -> str: """Create consistent keys for query string arguments. Produces the same cache key regardless of argument order, e.g., both `?limit=10&offset=20` and `?offset=20&limit=10` will always produce the same exact cache key. If func is provided and is callable it will be used to hash the function's source code and include it in the cache key. This will only be done is source_check is True. """ # Create a tuple of (key, value) pairs, where the key is the # argument name and the value is its respective value. Order # this tuple by key. Doing this ensures the cache key created # is always the same for query string args whose keys/values # are the same, regardless of the order in which they are # provided. if query_args is None: pairs: Iterable[tuple[str, str]] = request.args.items(multi=True) else: pairs = query_args_as_pairs(query_args) args_as_sorted_tuple = tuple(sorted(pair for pair in pairs)) # ... now hash the sorted (key, value) tuple so it can be # used as a key for cache. Turn them into bytes so that the # hash function will accept them args_as_bytes = str(args_as_sorted_tuple).encode() cache_hash = self._get_hash_method(hash_method)(args_as_bytes) # Use the source code if source_check is True and update the # cache_hash before generating the hashing and using it in # cache_key if self._get_source_check(source_check) and callable(f): func_source_code = inspect.getsource(f) cache_hash.update(func_source_code.encode("utf-8")) cache_hash = str(cache_hash.hexdigest()) if callable(key_prefix): cache_key = key_prefix() elif "%s" in key_prefix: cache_key = key_prefix % (request.path if path is None else path) else: cache_key = key_prefix return cache_key + cache_hash def _make_cache_key( args: tuple[Any, ...], kwargs: dict[str, Any], use_request: bool, path: str | None = None, query_args: _QueryArgs | None = None, ) -> str: if query_string: return _make_cache_key_query_string(path, query_args) else: cache_key: str if callable(key_prefix): cache_key = key_prefix() elif "%s" in key_prefix: if path is not None: cache_key = key_prefix % path elif use_request: cache_key = key_prefix % request.path else: # Outside of a request context ``url_for`` # defaults to an external URL, which would not # match the key the request stored. cache_key = key_prefix % url_for( f.__name__, _external=False, **kwargs ) else: cache_key = key_prefix if self._get_source_check(source_check) and callable(f): func_source_code = inspect.getsource(f) func_source_hash = self._get_hash_method(hash_method)( func_source_code.encode("utf-8") ) func_source_hash = str(func_source_hash.hexdigest()) cache_key += func_source_hash return cache_key cached_fn = cast("_CachedFunction[P, R]", decorated_function) cached_fn.uncached = f cached_fn.cache_timeout = timeout cached_fn.make_cache_key = default_make_cache_key return cached_fn return decorator def delete_cached( self, f: _AnyCachedFunction, path: str | None = None, query_args: _QueryArgs | None = None, **kwargs: Any, ) -> bool: """Delete the cached value of a :meth:`cached` decorated function. Example:: @app.route("/works") @cache.cached(query_string=True) def view_works(): return do_search(request.args) cache.delete_cached(view_works, "/works", {"limit": 15}) If you are calling this outside of a request context pass ``path`` and when the function was decorated with ``query_string=True`` you also have to pass ``query_args``. :param f: The decorated function whose cached value to delete. :param path: The ``request.path`` the value was cached for. :param query_args: The query string the value was cached for, as a query string, a mapping or an iterable of ``(key, value)`` pairs. Only used when the function was decorated with ``query_string=True``. :param kwargs: The view arguments, passed to ``url_for()`` to build the path when ``path`` is not given. """ return self.delete(f.make_cache_key(path=path, query_args=query_args, **kwargs)) def _memvname(self, funcname: str) -> str: return funcname + "_memver" def _memoize_make_version_hash(self) -> str: return base64.b64encode(uuid.uuid4().bytes)[:6].decode("utf-8") def _memoize_version( self, f: Callable[..., Any], args: Any | None = None, kwargs: dict[str, Any] | None = None, reset: bool = False, delete: bool = False, refresh: bool = False, timeout: int | None = VERSION_TIMEOUT, args_to_ignore: list[str] | None = None, ) -> tuple[str, str] | tuple[str, None]: """Updates the hash version associated with a memoized function or method. """ fname, instance_fname = function_namespace(f, args=args) version_key = self._memvname(fname) fetch_keys = [version_key] args_to_ignore = args_to_ignore or [] if "self" in args_to_ignore: instance_fname = None if instance_fname: instance_version_key = self._memvname(instance_fname) fetch_keys.append(instance_version_key) # Only delete the per-instance version key or per-function version # key but not both. if delete: self.delete_many(fetch_keys[-1]) return fname, None version_data_list = list(self.get_many(*fetch_keys)) dirty = refresh if version_data_list[0] is None: version_data_list[0] = self._memoize_make_version_hash() dirty = True if instance_fname and version_data_list[1] is None: version_data_list[1] = self._memoize_make_version_hash() dirty = True # Only reset the per-instance version or the per-function version # but not both. if reset: fetch_keys = fetch_keys[-1:] version_data_list = [self._memoize_make_version_hash()] dirty = True if dirty: self.set_many( dict(zip(fetch_keys, version_data_list, strict=False)), timeout=timeout, ) return fname, "".join(version_data_list) def _memoize_make_cache_key( self, make_name: Callable[..., str] | None = None, hash_method: Callable[..., Any] | None = None, source_check: bool | None = None, args_to_ignore: list[str] | None = None, ) -> Callable[..., str]: """Function used to create the cache_key for memoized functions.""" def make_cache_key(f: Callable[..., Any], *args: Any, **kwargs: Any) -> str: fname, version_data = self._memoize_version( f, args=args, kwargs=kwargs, args_to_ignore=args_to_ignore, ) #: this should have to be after version_data, so that it #: does not break the delete_memoized functionality. altfname = make_name(fname) if callable(make_name) else fname if callable(f): keyargs, keykwargs = self._memoize_kwargs_to_args( f, *args, **kwargs, args_to_ignore=args_to_ignore ) else: keyargs, keykwargs = args, kwargs updated = f"{altfname}{keyargs}{keykwargs}" cache_hash = self._get_hash_method(hash_method)() cache_hash.update(updated.encode("utf-8")) # Use the source code if source_check is True and update the # cache_key with the function's source. if self._get_source_check(source_check) and callable(f): func_source_code = inspect.getsource(f) cache_hash.update(func_source_code.encode("utf-8")) cache_key = base64.b64encode(cache_hash.digest())[:16].decode("utf-8") cache_key += version_data or "" return cache_key return make_cache_key def _memoize_kwargs_to_args( self, f: Callable[..., Any], *args: Any, **kwargs: Any ) -> Any: #: Inspect the arguments to the function #: This allows the memoization to be the same #: whether the function was called with #: 1, b=2 is equivalent to a=1, b=2, etc. new_args = [] arg_num = 0 args_to_ignore = kwargs.pop("args_to_ignore", None) or [] # If the function uses VAR_KEYWORD type of parameters, # we need to pass these further kw_keys_remaining = [key for key in kwargs if key not in args_to_ignore] arg_names = get_arg_names(f) args_len = len(arg_names) for i in range(args_len): arg_default = get_arg_default(f, i) if arg_names[i] in args_to_ignore: arg = None arg_num += 1 elif i == 0 and arg_names[i] in ("self", "cls"): #: use the id func of the class instance #: this supports instance methods for #: the memoized functions, giving more #: flexibility to developers if not args: raise ValueError( "When using `delete_memoized` on a " f"`{'classmethod' if arg_names[i] == 'cls' else 'method'}` " f"you must provide the `{arg_names[i]}` argument " "as the first positional argument." ) arg = get_id(args[0]) arg_num += 1 elif arg_names[i] in kwargs: arg = kwargs[arg_names[i]] kw_keys_remaining.pop(kw_keys_remaining.index(arg_names[i])) elif arg_num < len(args): arg = args[arg_num] arg_num += 1 elif arg_default is not None: arg = arg_default arg_num += 1 else: arg = None arg_num += 1 #: Attempt to convert all arguments to a #: hash/id or a representation? #: Not sure if this is necessary, since #: using objects as keys gets tricky quickly. # if hasattr(arg, '__class__'): # try: # arg = hash(arg) # except: # arg = get_id(arg) #: Or what about a special __cacherepr__ function #: on an object, this allows objects to act normal #: upon inspection, yet they can define a representation #: that can be used to make the object unique in the #: cache key. Given that a case comes across that #: an object "must" be used as a cache key # if hasattr(arg, '__cacherepr__'): # arg = arg.__cacherepr__ new_args.append(arg) new_args.extend(args[len(arg_names) :]) return ( tuple(new_args), OrderedDict( sorted((k, v) for k, v in kwargs.items() if k in kw_keys_remaining) ), ) def _bypass_cache( self, unless: Callable[..., Any] | None, f: Callable[..., Any], *args: Any, **kwargs: Any, ) -> bool: """Determines whether or not to bypass the cache by calling unless(). Supports both unless() that takes in arguments and unless() that doesn't. """ bypass_cache = False if callable(unless): # If unless() takes args, pass them in. if wants_args(unless): if unless(f, *args, **kwargs) is True: bypass_cache = True elif unless() is True: bypass_cache = True return bypass_cache def _forced_update( self, forced_update: Callable[..., Any] | None, *args: Any, **kwargs: Any, ) -> bool: if not callable(forced_update): return False # If forced_update() takes args, pass them in. if wants_args(forced_update): return forced_update(*args, **kwargs) is True return forced_update() is True def _is_stale( self, is_stale: Callable[..., Any] | None, rv: Any, *args: Any, **kwargs: Any, ) -> bool: if not callable(is_stale): return False # If is_stale() takes args besides the cached value, pass them in. if wants_extra_args(is_stale): return is_stale(rv, *args, **kwargs) is True return is_stale(rv) is True def memoize( self, timeout: _Timeout | None = None, make_name: Callable[..., str] | None = None, unless: Callable[..., bool] | None = None, forced_update: Callable[..., bool] | None = None, is_stale: Callable[..., bool] | None = None, response_filter: Callable[..., Any] | None = None, hash_method: Callable[..., Any] | None = None, cache_none: bool = False, source_check: bool | None = None, args_to_ignore: list[str] | None = None, ) -> Callable[[Callable[P, R]], _MemoizedFunction[P, R]]: """Use this to cache the result of a function, taking its arguments into account in the cache key. Information on `Memoization `_. Example:: @cache.memoize(timeout=50) def big_foo(a, b): return a + b + random.randrange(0, 1000) .. code-block:: pycon >>> big_foo(5, 2) 753 >>> big_foo(5, 3) 234 >>> big_foo(5, 2) 753 .. versionadded:: 0.4 The returned decorated function now has three function attributes assigned to it. **uncached** The original undecorated function. readable only **cache_timeout** The cache timeout value for this function. For a custom value to take affect, this must be set before the function is called. readable and writable **make_cache_key** A function used in generating the cache_key used. readable and writable :param timeout: Default None. If set to an integer, will cache for that amount of time. Unit of time is in seconds. A ``datetime.timedelta`` is also accepted and is rounded up to whole seconds. :param make_name: Default None. If set this is a function that accepts a single argument, the function name, and returns a new string to be used as the function name. If not set then the function name is used. :param unless: Default None. Cache will *always* execute the caching facilities unless this callable is true. This will bypass the caching entirely. :param forced_update: Default None. If this callable is true, cache value will be updated regardless cache is expired or not. Useful for background renewal of cached functions. :param is_stale: Default None. Called on a cache hit with the cached value as its first argument. If it is true the cached value will be recomputed. If the callable accepts more than one argument, the calls own arguments are passed after the cached value. :param response_filter: Default None. If not None, the callable is invoked after the cached funtion evaluation, and is given one arguement, the response content. If the callable returns False, the content will not be cached. Useful to prevent caching of code 500 responses. :param hash_method: Default None. If ``None``, the value is set by ``CACHE_HASH_METHOD``, which itself defaults to ``hashlib.sha256``. The hash method used to generate the keys for cached results. :param cache_none: Default False. If set to True, add a key exists check when cache.get returns None. This will likely lead to wrongly returned None values in concurrent situations and is not recommended to use. :param source_check: Default None. If None will use the value set by CACHE_SOURCE_CHECK. If True, include the function's source code in the hash to avoid using cached values when the source code has changed and the input values remain the same. This ensures that the cache_key will be formed with the function's source code hash in addition to other parameters that may be included in the formation of the key. :param args_to_ignore: List of arguments that will be ignored while generating the cache key. Default to None. This means that those arguments may change without affecting the cache value that will be returned. .. versionadded:: 0.5 params ``make_name``, ``unless`` .. versionadded:: 1.10 params ``args_to_ignore`` """ def memoize(f: Callable[P, R]) -> _MemoizedFunction[P, R]: @functools.wraps(f) def decorated_function(*args: Any, **kwargs: Any) -> Any: #: bypass cache if self._bypass_cache(unless, f, *args, **kwargs): return self._call_fn(f, *args, **kwargs) try: forced_update_result = self._forced_update( forced_update, *args, **kwargs ) cache_key = self._memoize_make_cache_key( make_name=make_name, hash_method=hash_method, source_check=source_check, args_to_ignore=args_to_ignore, )(f, *args, **kwargs) if forced_update_result: rv = None found = False else: rv = self.get(cache_key) found = True # If the value returned by cache.get() is None, it # might be because the key is not found in the cache # or because the cached value is actually None if rv is None: # If we're sure we don't need to cache None values # (cache_none=False), don't bother checking for # key existence, as it can lead to false positives # if a concurrent call already cached the # key between steps. This would cause us to # return None when we shouldn't if not cache_none: found = False else: found = self.has(cache_key) if found and self._is_stale(is_stale, rv, *args, **kwargs): rv = None found = False except Exception: if self.app.debug: raise logger.exception("Exception possibly due to cache backend.") return self._call_fn(f, *args, **kwargs) if self.enable_signals: signal = cache_memoize_hit if found else cache_memoize_miss signal.send( cache=self, cache_key=cache_key, f=f, args=args, kwargs=kwargs ) if not found: rv = self._call_fn(f, *args, **kwargs) if inspect.isgenerator(rv): rv = join_generator(rv) if response_filter is None or response_filter(rv): cache_timeout = normalize_timeout(memoized_fn.cache_timeout) try: self.set( cache_key, rv, timeout=cache_timeout, ) # update the memoize version with the new cache_timeout self._memoize_version( f, args=args, kwargs=kwargs, refresh=True, timeout=cache_timeout, args_to_ignore=args_to_ignore, ) except Exception: if self.app.debug: raise logger.exception("Exception possibly due to cache backend.") return rv memoized_fn = cast("_MemoizedFunction[P, R]", decorated_function) memoized_fn.uncached = f memoized_fn.cache_timeout = timeout memoized_fn.make_cache_key = self._memoize_make_cache_key( make_name=make_name, hash_method=hash_method, source_check=source_check, args_to_ignore=args_to_ignore, ) # Equivalent to passing ``f``: ``function_namespace`` reads only # dunders copied by ``functools.wraps`` and a signature that # follows ``__wrapped__``. memoized_fn.delete_memoized = lambda: self.delete_memoized(memoized_fn) return memoized_fn return memoize def delete_memoized( self, f: _AnyMemoizedFunction, *args: Any, **kwargs: Any ) -> None: """Deletes the specified functions caches, based by given parameters. If parameters are given, only the functions that were memoized with them will be erased. Otherwise all versions of the caches will be forgotten. Example:: @cache.memoize(50) def random_func(): return random.randrange(1, 50) @cache.memoize() def param_func(a, b): return a+b+random.randrange(1, 50) .. code-block:: pycon >>> random_func() 43 >>> random_func() 43 >>> cache.delete_memoized(random_func) >>> random_func() 16 >>> param_func(1, 2) 32 >>> param_func(1, 2) 32 >>> param_func(2, 2) 47 >>> cache.delete_memoized(param_func, 1, 2) >>> param_func(1, 2) 13 >>> param_func(2, 2) 47 Delete memoized is also smart about instance methods vs class methods. When passing a instancemethod, it will only clear the cache related to that instance of that object. (object uniqueness can be overridden by defining the __repr__ method, such as user id). When passing a classmethod, it will clear all caches related across all instances of that class. Example:: class Adder(object): @cache.memoize() def add(self, b): return b + random.random() .. code-block:: pycon >>> adder1 = Adder() >>> adder2 = Adder() >>> adder1.add(3) 3.23214234 >>> adder2.add(3) 3.60898509 >>> cache.delete_memoized(adder1.add) >>> adder1.add(3) 3.01348673 >>> adder2.add(3) 3.60898509 >>> cache.delete_memoized(Adder.add) >>> adder1.add(3) 3.53235667 >>> adder2.add(3) 3.72341788 Arguments narrow the deletion down to a single call. A bound method supplies its own instance, so only the remaining arguments are given: .. code-block:: pycon >>> cache.delete_memoized(adder1.add, 3) When the function is reached through the class instead, the instance has to be passed explicitly, the same way a class is passed for a ``@classmethod``: .. code-block:: pycon >>> cache.delete_memoized(Adder.add, adder1, 3) .. versionchanged:: 2.5.0 A bound method no longer needs to be given its own instance as the first argument. Passing it explicitly keeps working. :param fname: The memoized function. :param \\*args: A list of positional parameters used with memoized function. :param \\**kwargs: A dict of named parameters used with memoized function. .. note:: Flask-Caching uses inspect to order kwargs into positional args when the function is memoized. If you pass a function reference into ``fname``, Flask-Caching will be able to place the args/kwargs in the proper order, and delete the positional cache. However, if ``delete_memoized`` is just called with the name of the function, be sure to pass in potential arguments in the same order as defined in your function as args only, otherwise Flask-Caching will not be able to compute the same cache key and delete all memoized versions of it. .. note:: Flask-Caching maintains an internal random version hash for the function. Using delete_memoized will only swap out the version hash, causing the memoize function to recompute results and put them into another key. This leaves any computed caches for this memoized function within the caching backend. It is recommended to use a very high timeout with memoize if using this function, so that when the version hash is swapped, the old cached results would eventually be reclaimed by the caching backend. """ if not callable(f): raise TypeError( "Deleting messages by relative name is not supported, please " "use a function reference." ) if not (args or kwargs): self._memoize_version(f, reset=True) else: args = self._ensure_self_arg(f, args) cache_key = f.make_cache_key(f.uncached, *args, **kwargs) self.delete(cache_key) @staticmethod def _ensure_self_arg( f: _AnyMemoizedFunction, args: tuple[Any, ...] ) -> tuple[Any, ...]: """Supply the ``self`` argument when a bound method is passed. ``make_cache_key`` works on the undecorated function, so the instance has to be part of ``args`` to compute the same key that was used when the value was cached. A bound method already carries it, so passing it again is redundant:: cache.delete_memoized(adder.add, 3) Passing it explicitly keeps working, so that the form previously required for instance methods is not broken. """ instance = getattr(f, "__self__", None) if instance is None or inspect.isclass(instance): return args arg_names = get_arg_names(f.uncached) if not arg_names or arg_names[0] != "self" or (args and args[0] is instance): return args return (instance, *args) def delete_memoized_verhash(self, f: _AnyMemoizedFunction, *args: Any) -> None: """Delete the version hash associated with the function. .. warning:: Performing this operation could leave keys behind that have been created with this version hash. It is up to the application to make sure that all keys that may have been created with this version hash at least have timeouts so they will not sit orphaned in the cache backend. """ if not callable(f): raise TypeError( "Deleting messages by relative name is not supported, please" "use a function reference." ) self._memoize_version(f, delete=True) def __getattr__(name: str) -> Any: if name == "__version__": import importlib.metadata warnings.warn( "The '__version__' attribute is deprecated and will be removed in" " Flask-Caching 3.0. Use feature detection or" " 'importlib.metadata.version(\"flask-caching\")' instead.", DeprecationWarning, stacklevel=2, ) return importlib.metadata.version("flask-caching") raise AttributeError(name) pallets-eco-flask-caching-93fd601/src/flask_caching/backends/000077500000000000000000000000001524661224300240725ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/src/flask_caching/backends/__init__.py000066400000000000000000000020371524661224300262050ustar00rootroot00000000000000""" flask_caching.backends ~~~~~~~~~~~~~~~~~~~~~~ Various caching backends. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ from flask_caching.backends.base import BaseCache from flask_caching.backends.filesystemcache import FileSystemCache from flask_caching.backends.memcache import MemcachedCache from flask_caching.backends.memcache import SASLMemcachedCache from flask_caching.backends.memcache import SpreadSASLMemcachedCache from flask_caching.backends.nullcache import NullCache from flask_caching.backends.rediscache import RedisCache from flask_caching.backends.rediscache import RedisClusterCache from flask_caching.backends.rediscache import RedisSentinelCache from flask_caching.backends.simplecache import SimpleCache __all__ = ( "BaseCache", "NullCache", "SimpleCache", "FileSystemCache", "RedisCache", "RedisSentinelCache", "RedisClusterCache", "MemcachedCache", "SASLMemcachedCache", "SpreadSASLMemcachedCache", ) pallets-eco-flask-caching-93fd601/src/flask_caching/backends/base.py000066400000000000000000000027641524661224300253670ustar00rootroot00000000000000""" flask_caching.backends.base ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This module contains the BaseCache that other caching backends have to implement. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ from typing import Any from cachelib import BaseCache as CachelibBaseCache from flask import Flask class BaseCache(CachelibBaseCache): """Baseclass for the cache systems. All the cache systems implement this API or a superset of it. :param default_timeout: The default timeout (in seconds) that is used if no timeout is specified on ``set``. A timeout of 0 indicates that the cache never expires. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. """ def __init__( self, default_timeout: int = 300, ignore_delete_many_errors: bool = False ) -> None: CachelibBaseCache.__init__( self, default_timeout=default_timeout, ignore_delete_many_errors=ignore_delete_many_errors, ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "BaseCache": return cls() pallets-eco-flask-caching-93fd601/src/flask_caching/backends/filesystemcache.py000066400000000000000000000064331524661224300276220ustar00rootroot00000000000000""" flask_caching.backends.filesystem ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The filesystem caching backend. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ import hashlib import logging import warnings from typing import Any from cachelib import FileSystemCache as CachelibFileSystemCache from flask import Flask from flask_caching.backends.base import BaseCache logger = logging.getLogger(__name__) class FileSystemCache(BaseCache, CachelibFileSystemCache): """A cache that stores the items on the file system. This cache depends on being the only user of the ``cache_dir``. Make absolutely sure that nobody but this cache stores files there or otherwise the cache will randomly delete files therein. :param cache_dir: the directory where cache files are stored. :param threshold: the maximum number of items the cache stores before it starts deleting some. A threshold value of 0 indicates no threshold. :param default_timeout: the default timeout that is used if no timeout is specified on :meth:`~BaseCache.set`. A timeout of 0 indicates that the cache never expires. :param mode: the file mode wanted for the cache files, default 0600 :param hash_method: Default :func:`~hashlib.sha256`. The hash method used to generate the filename for cached results. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. :param ignore_errors: Deprecated alias for ``ignore_delete_many_errors``. .. deprecated:: 2.5.0 Will be removed in the next version. """ def __init__( self, cache_dir: str, threshold: int = 500, default_timeout: int = 300, mode: int = 0o600, hash_method: Any = hashlib.sha256, ignore_delete_many_errors: bool = False, ignore_errors: bool | None = None, ) -> None: if ignore_errors is not None: warnings.warn( "'ignore_errors' is deprecated and will be removed in the next " "version. Use 'ignore_delete_many_errors' instead.", DeprecationWarning, stacklevel=2, ) ignore_delete_many_errors = ignore_errors CachelibFileSystemCache.__init__( self, cache_dir=cache_dir, threshold=threshold, default_timeout=default_timeout, mode=mode, hash_method=hash_method, ignore_delete_many_errors=ignore_delete_many_errors, ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "FileSystemCache": args.insert(0, config["CACHE_DIR"]) kwargs.update( dict( threshold=config["CACHE_THRESHOLD"], hash_method=config["CACHE_FILE_HASH_METHOD"], ) ) return cls(*args, **kwargs) pallets-eco-flask-caching-93fd601/src/flask_caching/backends/memcache.py000066400000000000000000000222361524661224300262130ustar00rootroot00000000000000""" flask_caching.backends.memcache ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The memcache caching backend. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ import pickle import re from functools import partial from typing import Any from cachelib import MemcachedCache as CachelibMemcachedCache from flask import Flask from flask_caching.backends.base import BaseCache from flask_caching.utils import _Timeout _test_memcached_key = re.compile(r"[^\x00-\x21\xff]{1,250}$").match class MemcachedCache(BaseCache, CachelibMemcachedCache): """A cache that uses memcached as backend. The first argument can either be an object that resembles the API of a ``memcache.Client`` or a tuple/list of server addresses. In the event that a tuple/list is passed, CacheLib tries to import the best available memcache library. This cache looks into the following packages/modules to find bindings for memcached: - ``pylibmc`` - ``google.appengine.api.memcached`` - ``memcached`` - ``libmc`` Implementation notes: This cache backend works around some limitations in memcached to simplify the interface. For example unicode keys are encoded to UTF-8 on the fly. Methods such as :meth:`~.BaseCache.get_dict` return the keys in the same format as passed. Furthermore all get methods silently ignore key errors to not cause problems when untrusted user data is passed to the get methods which is often the case in web applications. This cache doesn't have a serializer since the underlying memcached client libraries handle serialization internally." :param servers: a list or tuple of server addresses or alternatively a ``memcache.Client`` or a compatible client. :param default_timeout: the default timeout that is used if no timeout is specified on :meth:`~BaseCache.set`. A timeout of 0 indicates that the cache never expires. :param key_prefix: a prefix that is added before all keys. This makes it possible to use the same memcached server for different applications. Keep in mind that :meth:`~.BaseCache.clear` will also clear keys with a different prefix. :param pool_size: the size of the connection pool. This is only used if the memcached client library supports connection pooling. .. versionadded:: 2.5.0 :param pool_blocking: if the connection pool is exhausted, should the client block until a connection is available or raise an exception. This is only used if the memcached client library supports connection pooling. .. versionadded:: 2.5.0 :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. .. versionadded:: 2.5.0 """ def __init__( self, servers: Any = None, default_timeout: int = 300, key_prefix: str | None = None, pool_size: int = 1, pool_blocking: bool = True, ignore_delete_many_errors: bool = False, ) -> None: CachelibMemcachedCache.__init__( self, servers=servers, default_timeout=default_timeout, key_prefix=key_prefix, pool_size=pool_size, pool_blocking=pool_blocking, ignore_delete_many_errors=ignore_delete_many_errors, ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "MemcachedCache": args.append(config["CACHE_MEMCACHED_SERVERS"]) kwargs.update(dict(key_prefix=config["CACHE_KEY_PREFIX"])) return cls(*args, **kwargs) class SASLMemcachedCache(MemcachedCache): def __init__( self, servers: Any = None, default_timeout: int = 300, key_prefix: str | None = None, username: str | None = None, password: str | None = None, pool_size: int = 1, pool_blocking: bool = True, ignore_delete_many_errors: bool = False, **kwargs: Any, ) -> None: super().__init__( default_timeout=default_timeout, ignore_delete_many_errors=ignore_delete_many_errors, ) if servers is None: servers = ["127.0.0.1:11211"] import pylibmc # # type: ignore pyright: ignore[reportMissingImports] client = pylibmc.Client( servers, username=username, password=password, binary=True, **kwargs ) pool = pylibmc.ClientPool(client, pool_size) reserve = partial(pool.reserve, block=pool_blocking) self._client = pool self._client_context = reserve self.key_prefix = key_prefix @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "SASLMemcachedCache": args.append(config["CACHE_MEMCACHED_SERVERS"]) kwargs.update( dict( username=config["CACHE_MEMCACHED_USERNAME"], password=config["CACHE_MEMCACHED_PASSWORD"], key_prefix=config["CACHE_KEY_PREFIX"], ) ) return cls(*args, **kwargs) class SpreadSASLMemcachedCache(SASLMemcachedCache): """Simple Subclass of SASLMemcached client that will spread the value across multiple keys if they are bigger than a given threshold. Spreading requires using pickle to store the value, which can significantly impact the performance. """ def __init__(self, *args: Any, **kwargs: Any) -> None: """ Kwargs: chunksize (int): max length of a pickled object that can fit in memcached (memcache has an upper limit of 1MB for values, default: 1048448) """ self.chunksize = kwargs.get("chunksize", 1048448) self.maxchunk = kwargs.get("maxchunk", 32) super().__init__(*args, **kwargs) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "SpreadSASLMemcachedCache": args.append(config["CACHE_MEMCACHED_SERVERS"]) kwargs.update( dict( username=config.get("CACHE_MEMCACHED_USERNAME"), password=config.get("CACHE_MEMCACHED_PASSWORD"), key_prefix=config.get("CACHE_KEY_PREFIX"), ) ) return cls(*args, **kwargs) def delete(self, key: str) -> Any: for skey in self._genkeys(key): super().delete(skey) def set( self, key: str, value: Any, timeout: _Timeout | None = None, chunk: bool = True, ) -> bool | None: """Set a value in cache, potentially spreading it across multiple key. :param key: The cache key. :param value: The value to cache. :param timeout: The timeout after which the cache will be invalidated. :param chunk: If set to `False`, then spreading across multiple keys is disabled. This can be faster, but it will fail if the value is bigger than the chunks. It requires you to get back the object by specifying that it is not spread. """ if chunk: self._set(key, value, timeout=timeout) return None else: return super().set(key, value, timeout=timeout) def _set(self, key: str, value: Any, timeout: _Timeout | None = None) -> None: # pickling/unpickling add an overhead, # I didn't found a good way to avoid pickling/unpickling if # key is smaller than chunksize, because in case or # getting the length consume the data iterator. serialized = pickle.dumps(value, 2) values = {} len_ser = len(serialized) chks = range(0, len_ser, self.chunksize) if len(chks) > self.maxchunk: raise ValueError(f"Cannot store value in less than {self.maxchunk} keys") for i in chks: values[f"{key}.{i // self.chunksize}"] = serialized[i : i + self.chunksize] super().set_many(values, timeout) def get(self, key: str, chunk: bool = True) -> Any: """Get a cached value. :param chunk: If set to ``False``, it will return a cached value that is spread across multiple keys. """ if chunk: return self._get(key) else: return super().get(key) def _genkeys(self, key: str) -> list[str]: return [f"{key}.{i}" for i in range(self.maxchunk)] def _get(self, key: str) -> Any: to_get = [f"{key}.{i}" for i in range(self.maxchunk)] result = super().get_many(*to_get) serialized = b"".join(v for v in result if v is not None) if not serialized: return None return pickle.loads(serialized) pallets-eco-flask-caching-93fd601/src/flask_caching/backends/nullcache.py000066400000000000000000000011471524661224300264050ustar00rootroot00000000000000""" flask_caching.backends.null ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The null cache backend. A caching backend that doesn't cache. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ from flask_caching.backends.base import BaseCache class NullCache(BaseCache): """A cache that doesn't cache. This can be useful for unit testing. :param default_timeout: a dummy parameter that is ignored but exists for API compatibility with other caches. """ def has(self, key: str) -> bool: return False pallets-eco-flask-caching-93fd601/src/flask_caching/backends/rediscache.py000066400000000000000000000261021524661224300265370ustar00rootroot00000000000000""" flask_caching.backends.rediscache ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The redis caching backend. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ from typing import Any from cachelib import RedisCache as CachelibRedisCache from flask import Flask from flask_caching.backends.base import BaseCache class RedisCache(BaseCache, CachelibRedisCache): """Uses the Redis key-value store as a cache backend. The first argument can be either a string denoting address of the Redis server or an object resembling an instance of a redis.Redis class. Note: Python Redis API already takes care of encoding unicode strings on the fly. :param host: address of the Redis server or an object which API is compatible with the official Python Redis client (``redis-py``). :param port: port number on which Redis server listens for connections. :param password: password authentication for the Redis server. :param db: db (zero-based numeric index) on Redis Server to connect. :param default_timeout: the default timeout that is used if no timeout is specified on :meth:`~BaseCache.set`. A timeout of 0 indicates that the cache never expires. :param key_prefix: A prefix that should be added to all keys. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. Any additional keyword arguments will be passed to ``redis.Redis``. """ def __init__( self, host: Any = "localhost", port: int = 6379, password: str | None = None, db: int = 0, default_timeout: int = 300, key_prefix: str | None = None, ignore_delete_many_errors: bool = False, **kwargs: Any, ) -> None: CachelibRedisCache.__init__( self, host=host, port=port, password=password, db=db, default_timeout=default_timeout, key_prefix=key_prefix, ignore_delete_many_errors=ignore_delete_many_errors, **kwargs, ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "RedisCache": try: from redis import from_url as redis_from_url except ImportError as e: raise RuntimeError("no redis module found") from e kwargs.update( dict( host=config.get("CACHE_REDIS_HOST", "localhost"), port=config.get("CACHE_REDIS_PORT", 6379), db=config.get("CACHE_REDIS_DB", 0), ) ) password = config.get("CACHE_REDIS_PASSWORD") if password: kwargs["password"] = password key_prefix = config.get("CACHE_KEY_PREFIX") if key_prefix: kwargs["key_prefix"] = key_prefix redis_url = config.get("CACHE_REDIS_URL") if redis_url: redis_kwargs = config.pop("CACHE_OPTIONS", None) or {} kwargs["host"] = redis_from_url( redis_url, db=kwargs.pop("db", None), **redis_kwargs ) new_class = cls(*args, **kwargs) return new_class def unlink(self, *keys: str) -> Any: """when redis-py >= 3.0.0 and redis > 4, support this operation""" if not keys: return None prefix = self._get_prefix() if prefix: keys = tuple(prefix + key for key in keys) unlink = getattr(self._write_client, "unlink", None) if unlink is not None and callable(unlink): return self._write_client.unlink(*keys) return self._write_client.delete(*keys) class RedisSentinelCache(RedisCache): """Uses the Redis key-value store as a cache backend. The first argument can be either a string denoting address of the Redis server or an object resembling an instance of a redis.Redis class. Note: Python Redis API already takes care of encoding unicode strings on the fly. :param sentinels: A list or a tuple of Redis sentinel addresses. :param master: The name of the master server in a sentinel configuration. :param password: password authentication for the Redis server. :param db: db (zero-based numeric index) on Redis Server to connect. :param default_timeout: the default timeout that is used if no timeout is specified on :meth:`~BaseCache.set`. A timeout of 0 indicates that the cache never expires. :param key_prefix: A prefix that should be added to all keys. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. Any additional keyword arguments will be passed to ``redis.sentinel.Sentinel``. """ def __init__( self, sentinels: Any = None, master: str = "mymaster", password: str | None = None, db: int = 0, default_timeout: int = 300, key_prefix: str = "", ignore_delete_many_errors: bool = False, **kwargs: Any, ) -> None: try: import redis.sentinel except ImportError as e: raise RuntimeError("no redis module found") from e if kwargs.get("decode_responses", None): raise ValueError("decode_responses is not supported by RedisCache.") sentinels = sentinels or [("127.0.0.1", 26379)] sentinel_kwargs = { key[9:]: value for key, value in kwargs.items() if key.startswith("sentinel_") } kwargs = { key: value for key, value in kwargs.items() if not key.startswith("sentinel_") } sentinel = redis.sentinel.Sentinel( sentinels=sentinels, password=password, db=db, sentinel_kwargs=sentinel_kwargs, **kwargs, ) write_client = sentinel.master_for(master) read_client = sentinel.slave_for(master) super().__init__( host=write_client, key_prefix=key_prefix, default_timeout=default_timeout, ignore_delete_many_errors=ignore_delete_many_errors, ) self._read_client = read_client @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "RedisSentinelCache": kwargs.update( dict( sentinels=config.get("CACHE_REDIS_SENTINELS", [("127.0.0.1", 26379)]), master=config.get("CACHE_REDIS_SENTINEL_MASTER", "mymaster"), password=config.get("CACHE_REDIS_PASSWORD", None), sentinel_password=config.get("CACHE_REDIS_SENTINEL_PASSWORD", None), key_prefix=config.get("CACHE_KEY_PREFIX", None), db=config.get("CACHE_REDIS_DB", 0), ) ) return cls(*args, **kwargs) class RedisClusterCache(RedisCache): """Uses the Redis key-value store as a cache backend. The first argument can be either a string denoting address of the Redis server or an object resembling an instance of a rediscluster.RedisCluster class. Note: Python Redis API already takes care of encoding unicode strings on the fly. :param cluster: The redis cluster nodes address separated by comma. e.g. host1:port1,host2:port2,host3:port3 . :param password: password authentication for the Redis server. :param default_timeout: the default timeout that is used if no timeout is specified on :meth:`~BaseCache.set`. A timeout of 0 indicates that the cache never expires. :param key_prefix: A prefix that should be added to all keys. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. Any additional keyword arguments will be passed to ``rediscluster.RedisCluster``. """ def __init__( self, cluster: Any = "", password: str = "", default_timeout: int = 300, key_prefix: str = "", ignore_delete_many_errors: bool = False, **kwargs: Any, ) -> None: if kwargs.get("decode_responses", None): raise ValueError("decode_responses is not supported by RedisCache.") try: from redis import RedisCluster from redis.cluster import ClusterNode except ImportError as e: raise RuntimeError("no redis.cluster module found") from e redis_url = kwargs.pop("redis_url", None) # Use URL-based connection if provided, otherwise use startup nodes. if redis_url: cluster = RedisCluster.from_url(redis_url, **kwargs) else: try: nodes = [(node.split(":")) for node in cluster.split(",")] startup_nodes = [ ClusterNode(node[0].strip(), int(node[1].strip())) for node in nodes ] except IndexError as e: raise ValueError( "Please give the correct cluster argument " "e.g. host1:port1,host2:port2,host3:port3" ) from e # Skips the check of cluster-require-full-coverage config, # useful for clusters without the CONFIG command (like aws) skip_full_coverage_check = kwargs.pop("skip_full_coverage_check", True) cluster = RedisCluster( startup_nodes=startup_nodes, password=password, skip_full_coverage_check=skip_full_coverage_check, **kwargs, ) super().__init__( host=cluster, key_prefix=key_prefix, default_timeout=default_timeout, ignore_delete_many_errors=ignore_delete_many_errors, ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "RedisClusterCache": kwargs.update( dict( cluster=config.get("CACHE_REDIS_CLUSTER", ""), password=config.get("CACHE_REDIS_PASSWORD", ""), default_timeout=config.get("CACHE_DEFAULT_TIMEOUT", 300), key_prefix=config.get("CACHE_KEY_PREFIX", ""), redis_url=config.get("CACHE_REDIS_URL", ""), ) ) return cls(*args, **kwargs) pallets-eco-flask-caching-93fd601/src/flask_caching/backends/simplecache.py000066400000000000000000000047511524661224300267300ustar00rootroot00000000000000""" flask_caching.backends.simple ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The simple cache backend. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ import logging import warnings from typing import Any from cachelib import SimpleCache as CachelibSimpleCache from flask import Flask from flask_caching.backends.base import BaseCache logger = logging.getLogger(__name__) class SimpleCache(BaseCache, CachelibSimpleCache): """Simple memory cache for single process environments. All operations are protected by a :class:`threading.RLock`, making a cache instance safe to use from multiple threads within the same process. :param threshold: the maximum number of items the cache stores before it starts deleting some. :param default_timeout: the default timeout that is used if no timeout is specified on ``set``. A timeout of 0 indicates that the cache never expires. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. :param ignore_errors: Deprecated alias for ``ignore_delete_many_errors``. .. deprecated:: 2.5.0 Will be removed in the next version. """ def __init__( self, threshold: int = 500, default_timeout: int = 300, ignore_delete_many_errors: bool = False, ignore_errors: bool | None = None, ) -> None: if ignore_errors is not None: warnings.warn( "'ignore_errors' is deprecated and will be removed in the next " "version. Use 'ignore_delete_many_errors' instead.", DeprecationWarning, stacklevel=2, ) ignore_delete_many_errors = ignore_errors CachelibSimpleCache.__init__( self, threshold=threshold, default_timeout=default_timeout, ignore_delete_many_errors=ignore_delete_many_errors, ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "SimpleCache": kwargs.update(dict(threshold=config["CACHE_THRESHOLD"])) return cls(*args, **kwargs) pallets-eco-flask-caching-93fd601/src/flask_caching/contrib/000077500000000000000000000000001524661224300237605ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/src/flask_caching/contrib/__init__.py000066400000000000000000000000001524661224300260570ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/src/flask_caching/contrib/googlecloudstoragecache.py000066400000000000000000000177741524661224300312260ustar00rootroot00000000000000import datetime import json import logging from collections.abc import Iterable from typing import Any from flask import Flask from flask_caching.backends.base import BaseCache from flask_caching.utils import _Timeout logger = logging.getLogger(__name__) try: from google.auth.credentials import ( # pyright: ignore[reportMissingImports] AnonymousCredentials, ) from google.cloud import exceptions # pyright: ignore[reportAttributeAccessIssue] from google.cloud import storage # pyright: ignore[reportAttributeAccessIssue] except ImportError as e: raise RuntimeError("no google-cloud-storage module found") from e class GoogleCloudStorageCache(BaseCache): """Uses an Google Cloud Storage bucket as a cache backend. Note: User-contributed functionality. This project does not guarantee that this functionality will be maintained or functional at any given time. Note: Cache keys must meet GCS criteria for a valid object name (a sequence of Unicode characters whose UTF-8 encoding is at most 1024 bytes long). Note: Expired cache objects are not automatically purged. If delete_expired_objects_on_read=True, they will be deleted following an attempted read (which reduces performance). Otherwise, you have to delete stale objects yourself. Consider an GCS bucket lifecycle rule or other out-of-band process. For example you can use the following rule. {"rule": [{"action": {"type": "Delete"}, "condition": {"daysSinceCustomTime": 0}}]} https://cloud.google.com/storage/docs/lifecycle#dayssincecustomtime :param bucket: Required. Name of the bucket to use. It must already exist. :param key_prefix: A prefix that should be added to all keys. :param default_timeout: the default timeout that is used if no timeout is specified on :meth:`~BaseCache.set`. A timeout of 0 indicates that the cache never expires. :param delete_expired_objects_on_read: If True, if a read finds a stale object, it will be deleted before a response is returned. Will slow down responses. :param anonymous: If true, use anonymous credentials. Useful for testing. Any additional keyword arguments will be passed to ``google.cloud.storage.Client``. """ def __init__( self, bucket: str, key_prefix: str | None = None, default_timeout: int = 300, delete_expired_objects_on_read: bool = False, anonymous: bool = False, **kwargs: Any, ) -> None: super().__init__(default_timeout) if not isinstance(bucket, str): raise ValueError("GCSCache bucket parameter must be a string") if anonymous: self._client = storage.Client( credentials=AnonymousCredentials(), project="test", **kwargs ) else: self._client = storage.Client(**kwargs) self.bucket = self._client.get_bucket(bucket) self.key_prefix = key_prefix or "" self.default_timeout = default_timeout self.delete_expired_objects_on_read = delete_expired_objects_on_read @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "GoogleCloudStorageCache": args.insert(0, config["CACHE_GCS_BUCKET"]) key_prefix = config.get("CACHE_KEY_PREFIX") if key_prefix: kwargs["key_prefix"] = key_prefix return cls(*args, **kwargs) def get(self, key: str) -> Any: result = None expired = False hit_or_miss = "miss" full_key = self.key_prefix + key blob = self.bucket.get_blob(full_key) if blob is not None: expired = blob.custom_time and self._now() > blob.custom_time if expired: # Object is stale if self.delete_expired_objects_on_read: self._delete(full_key) else: try: result = blob.download_as_bytes() hit_or_miss = "hit" if blob.content_type == "application/json": result = json.loads(result) except exceptions.NotFound: pass expiredstr = "(expired)" if expired else "" logger.debug("get key %r -> %s %s", full_key, hit_or_miss, expiredstr) return result def set(self, key: str, value: Any, timeout: _Timeout | None = None) -> bool: result = False full_key = self.key_prefix + key content_type = "application/json" try: value = json.dumps(value) except (UnicodeDecodeError, TypeError): content_type = "application/octet-stream" blob = self.bucket.blob(full_key) seconds = self._normalize_timeout(timeout) if seconds != 0: # Use 'Custom-Time' for expiry # https://cloud.google.com/storage/docs/metadata#custom-time blob.custom_time = self._now(delta=seconds) try: blob.upload_from_string(value, content_type=content_type) result = True except exceptions.TooManyRequests: pass logger.debug("set key %r -> %s", full_key, result) return result def add(self, key: str, value: Any, timeout: _Timeout | None = None) -> bool: full_key = self.key_prefix + key if self._has(full_key): logger.debug("add key %r -> not added", full_key) return False else: return self.set(key, value, timeout) def delete(self, key: str) -> bool: full_key = self.key_prefix + key return self._delete(full_key) def delete_many(self, *keys: str) -> Any: return self._delete_many(self.key_prefix + key for key in keys) def has(self, key: str) -> bool: full_key = self.key_prefix + key return self._has(full_key) def clear(self) -> bool: return self._prune(clear_all=True) def _prune(self, clear_all: bool = False) -> bool: # Delete in batches of 100 which is much faster than individual deletes nremoved = 0 now = self._now() response_iterator = self._client.list_blobs( self.bucket, prefix=self.key_prefix, fields="items(name,customTime),nextPageToken", ) to_delete = [] for blob in response_iterator: if clear_all or blob.custom_time and blob.custom_time < now: to_delete.append(blob.name) nremoved += 1 if len(to_delete) == 100: self._delete_many(to_delete) to_delete = [] # Delete the remainder if to_delete: self._delete_many(to_delete) logger.debug("evicted %d key(s)", nremoved) return True def _delete(self, key: str) -> bool: return self._delete_many([key]) def _delete_many(self, keys: Iterable[str]) -> bool: try: with self._client.batch(): for key in keys: self.bucket.delete_blob(key) except (exceptions.NotFound, exceptions.TooManyRequests): pass return True def _has(self, key: str) -> bool: result = False expired = False blob = self.bucket.get_blob(key) if blob is not None: expired = blob.custom_time and self._now() > blob.custom_time if expired: # Exists but is stale if self.delete_expired_objects_on_read: self._delete(key) else: result = True expiredstr = "(expired)" if expired else "" logger.debug("has key %r -> %s %s", key, result, expiredstr) return result def _now(self, delta: int = 0) -> datetime.datetime: return datetime.datetime.now(datetime.UTC) + datetime.timedelta(seconds=delta) pallets-eco-flask-caching-93fd601/src/flask_caching/contrib/uwsgicache.py000066400000000000000000000054011524661224300264540ustar00rootroot00000000000000""" flask_caching.backends.uwsgicache ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The uWSGI caching backend. :copyright: (c) 2018 by Peter Justin. :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ from typing import Any from cachelib import UWSGICache as CachelibUWSGICache from flask import Flask from flask_caching.backends.base import BaseCache class UWSGICache(BaseCache, CachelibUWSGICache): """Implements the cache using uWSGI's caching framework. .. note:: This class cannot be used when running under PyPy, because the uWSGI API implementation for PyPy is lacking the needed functionality. :param default_timeout: The default timeout in seconds. :param cache: The name of the caching instance to connect to, for example: mycache@localhost:3031, defaults to an empty string, which means uWSGI will use the first cache instance initialized. If the cache is in the same instance as the werkzeug app, you only have to provide the name of the cache. :param ignore_delete_many_errors: If set to ``False`` the ``delete_many`` method raises a ``RuntimeError`` in case a key couldn't be deleted. Defaults to ``False``. """ def __init__( self, default_timeout: int = 300, cache: str = "", ignore_delete_many_errors: bool = False, ) -> None: CachelibUWSGICache.__init__( self, cache=cache, default_timeout=default_timeout, ignore_delete_many_errors=ignore_delete_many_errors, ) try: import uwsgi self._uwsgi = uwsgi except ImportError as e: raise RuntimeError( "uWSGI could not be imported, are you running under uWSGI?" ) from e if "cache2" not in uwsgi.opt: raise RuntimeError( "You must enable cache2 in uWSGI configuration: " "https://uwsgi-docs.readthedocs.io/en/latest/Caching.html" ) @classmethod def factory( cls, app: Flask, config: dict[str, Any], args: list[Any], kwargs: dict[str, Any], ) -> "UWSGICache": # The name of the caching instance to connect to, for # example: mycache@localhost:3031, defaults to an empty string, which # means uWSGI will cache in the local instance. If the cache is in the # same instance as the werkzeug app, you only have to provide the name # of the cache. uwsgi_cache_name = config.get("CACHE_UWSGI_NAME", "") kwargs.update(dict(cache=uwsgi_cache_name)) return cls(*args, **kwargs) pallets-eco-flask-caching-93fd601/src/flask_caching/jinja2ext.py000066400000000000000000000066361524661224300246030ustar00rootroot00000000000000""" flask_caching.jinja2ext ~~~~~~~~~~~~~~~~~~~~~~~ Jinja2 extension that adds support for caching template fragments. Usage:: {% cache timeout key1[, [key2, ...]] %} ... {% endcache %} By default, the value of "path to template file" + "block start line" is used as the cache key. Also, the key name can be set manually. Keys are concatenated together into a single string, that can be used to avoid the same block evaluating in different templates. Set the timeout to ``None`` for no timeout, but with custom keys:: {% cache None "key" %} ... {% endcache %} Set the timeout to ``del`` to delete the cached value:: {% cache 'del' key1 %} ... {% endcache %} Considering we have ``render_form_field`` and ``render_submit`` macros:: {% cache 60*5 'myform' %}
{% render_form_field(form.username) %} {% render_submit() %}
{% endcache %} :copyright: (c) 2010 by Thadeus Burgess. :license: BSD, see LICENSE for more details. """ from collections.abc import Callable from typing import Any from jinja2 import nodes from jinja2.ext import Extension from jinja2.parser import Parser from flask_caching import make_template_fragment_key from flask_caching.utils import _Timeout from flask_caching.utils import normalize_timeout JINJA_CACHE_ATTR_NAME = "_template_fragment_cache" class CacheExtension(Extension): tags = {"cache"} def parse(self, parser: Parser) -> nodes.Node: lineno = next(parser.stream).lineno #: Parse timeout args = [parser.parse_expression()] #: Parse fragment name #: Grab the fragment name if it exists #: otherwise, default to the old method of using the template's #: lineno to maintain backwards compatibility. if parser.stream.skip_if("comma"): args.append(parser.parse_expression()) else: args.append(nodes.Const(f"{parser.filename}{lineno}")) #: Parse vary_on parameters vary_on: list[nodes.Expr] = [] while parser.stream.skip_if("comma"): vary_on.append(parser.parse_expression()) if vary_on: args.append(nodes.List(vary_on)) else: args.append(nodes.Const([])) body = parser.parse_statements(("name:endcache",), drop_needle=True) return nodes.CallBlock( self.call_method("_cache", args), [], [], body ).set_lineno(lineno) def _cache( self, timeout: _Timeout | str | None, fragment_name: str, vary_on: list[str], caller: Callable[[], str], ) -> Any: cache = getattr(self.environment, JINJA_CACHE_ATTR_NAME) key = make_template_fragment_key(fragment_name, vary_on=vary_on) #: Delete key if timeout is 'del' if timeout == "del": try: cache.delete(key) return caller() except Exception as e: if cache.app.debug: raise e else: return caller() try: rv = cache.get(key) if rv is None: rv = caller() seconds = normalize_timeout(timeout) cache.set(key, rv, timeout=seconds) return rv except Exception as e: if cache.app.debug: raise e else: return caller() pallets-eco-flask-caching-93fd601/src/flask_caching/py.typed000066400000000000000000000000001524661224300240050ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/src/flask_caching/signals.py000066400000000000000000000023601524661224300243330ustar00rootroot00000000000000""" flask_caching.signals ~~~~~~~~~~~~~~~~~~~~~ The signals for Flask-Caching. :copyright: (c) 2026 by Peter Justin :license: BSD, see LICENSE for more details. """ from blinker import Namespace _signals = Namespace() #: Sent when a view decorated with :meth:`~Cache.cached` is served from the #: cache. It is passed ``cache``, the :class:`Cache` instance, ``cache_key``, #: the key the response was found under, and ``args`` and ``kwargs``, the #: arguments the view was called with. cache_view_hit = _signals.signal("cache-view-hit") #: Sent when a view decorated with :meth:`~Cache.cached` is not found in the #: cache and has to be called. It is passed the same arguments as #: :data:`cache_view_hit`. cache_view_miss = _signals.signal("cache-view-miss") #: Sent when a function decorated with :meth:`~Cache.memoize` is served from #: the cache. In addition to the arguments passed to :data:`cache_view_hit`, #: it is passed ``f``, the undecorated function. cache_memoize_hit = _signals.signal("cache-memoize-hit") #: Sent when a function decorated with :meth:`~Cache.memoize` is not found in #: the cache and has to be called. It is passed the same arguments as #: :data:`cache_memoize_hit`. cache_memoize_miss = _signals.signal("cache-memoize-miss") pallets-eco-flask-caching-93fd601/src/flask_caching/utils.py000066400000000000000000000132401524661224300240320ustar00rootroot00000000000000import inspect import math import string import sys from collections.abc import Callable from collections.abc import Iterable from collections.abc import Mapping from datetime import timedelta from typing import Any from typing import cast from typing import TypeAlias from urllib.parse import parse_qsl from werkzeug.datastructures import MultiDict if sys.version_info >= (3, 14): import annotationlib def _signature(f: Callable) -> inspect.Signature: return inspect.signature(f, annotation_format=annotationlib.Format.FORWARDREF) else: _signature = inspect.signature TEMPLATE_FRAGMENT_KEY_TEMPLATE = "_template_fragment_cache_%s%s" # Used to remove control characters and whitespace from cache keys. valid_chars = set(string.ascii_letters + string.digits + "_.") del_chars = "".join(c for c in map(chr, range(256)) if c not in valid_chars) null_control = str.maketrans({k: None for k in del_chars}) _QueryArgs: TypeAlias = str | Mapping[str, Any] | Iterable[tuple[str, Any]] _Timeout: TypeAlias = int | timedelta def normalize_timeout(timeout: _Timeout | str | None) -> int | None: """Convert a ``timedelta`` timeout to whole seconds. A subsecond timeout (i.e. miliseconds) is rounded up to a whole second. A subsecond timeout does not turn into ``0``! """ if isinstance(timeout, str): timeout = int(timeout) if isinstance(timeout, timedelta): return math.ceil(timeout.total_seconds()) return timeout def wants_args(f: Callable[..., Any]) -> bool: """Check if the function wants any positional, *args, or **kwargs arguments.""" return any( p.kind != inspect.Parameter.KEYWORD_ONLY for p in get_function_parameters(f) ) def wants_extra_args(f: Callable[..., Any]) -> bool: """Check if the function wants an additional argument beside its first positional one. """ return ( sum( p.kind != inspect.Parameter.KEYWORD_ONLY for p in get_function_parameters(f) ) > 1 ) def get_function_parameters(f: Callable[..., Any]) -> list[inspect.Parameter]: """Get function parameters :param f :return: Parameter list of function """ return list(_signature(f).parameters.values()) def get_arg_names(f: Callable[..., Any]) -> list[str]: """Return arguments of function :param f: :return: String list of arguments """ return [ parameter.name for parameter in get_function_parameters(f) if parameter.kind == parameter.POSITIONAL_OR_KEYWORD ] def get_arg_default(f: Callable[..., Any], position: int) -> Any: arg = get_function_parameters(f)[position] arg_def = arg.default return arg_def if arg_def != inspect.Parameter.empty else None def query_args_as_pairs(query_args: _QueryArgs) -> list[tuple[str, str]]: """Convert the accepted query args to (key, value) pairs.""" if isinstance(query_args, str): return parse_qsl(query_args.lstrip("?"), keep_blank_values=True) if isinstance(query_args, MultiDict): items: Iterable[tuple[str, Any]] = cast( "MultiDict[str, Any]", query_args ).items(multi=True) elif isinstance(query_args, Mapping): items = cast("Mapping[str, Any]", query_args).items() else: items = query_args return [(key, str(value)) for key, value in items] def get_id(obj: Any) -> str: caching_id = getattr(obj, "__caching_id__", None) if caching_id is None: return repr(obj) return str(caching_id()) def function_namespace( f: Callable[..., Any], args: Any = None ) -> tuple[str, str | None]: """Attempts to return a unique namespace for the function.""" m_args = get_arg_names(f) instance_token = None instance_self = getattr(f, "__self__", None) if instance_self and not inspect.isclass(instance_self): instance_token = get_id(instance_self) elif m_args and m_args[0] == "self" and args: instance_token = get_id(args[0]) module = f.__module__ if m_args and m_args[0] == "cls" and not (args and inspect.isclass(args[0])): raise ValueError( "When using `delete_memoized` on a " "`@classmethod` you must provide the " "class as the first argument" ) if hasattr(f, "__qualname__"): name = f.__qualname__ else: klass = getattr(f, "__self__", None) if klass and not inspect.isclass(klass): klass = klass.__class__ if not klass: klass = getattr(f, "im_class", None) if not klass: if m_args and args: if m_args[0] == "self": klass = args[0].__class__ elif m_args[0] == "cls": klass = args[0] if klass: name = klass.__name__ + "." + f.__name__ else: name = f.__name__ ns = ".".join((module, name)).translate(null_control) ins = ( ".".join((module, name, instance_token)).translate(null_control) if instance_token else None ) return ns, ins def make_template_fragment_key( fragment_name: str, vary_on: list[str] | None = None ) -> str: """Make a cache key for a specific fragment name.""" if vary_on: fragment_name = f"{fragment_name}_" else: vary_on = [] return TEMPLATE_FRAGMENT_KEY_TEMPLATE % (fragment_name, "_".join(vary_on)) def join_generator(rv: Any) -> Any: """Used to join strings and bytes together when inspect.isgenerator(rv) = True""" chunks = list(rv) if all(isinstance(chunk, str) for chunk in chunks): return "".join(chunks) if all(isinstance(chunk, bytes) for chunk in chunks): return b"".join(chunks) return chunks pallets-eco-flask-caching-93fd601/tests/000077500000000000000000000000001524661224300201175ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/tests/conftest.py000066400000000000000000000060571524661224300223260ustar00rootroot00000000000000import os import shutil import socket import subprocess import cachelib.file import cachelib.simple import flask import pytest from xprocess import ProcessStarter import flask_caching as fsc @pytest.fixture def app(request): app = flask.Flask( request.module.__name__, template_folder=os.path.dirname(__file__) ) app.testing = True app.config["CACHE_TYPE"] = "SimpleCache" return app @pytest.fixture def cache(app): return fsc.Cache(app) class _Clock: def __init__(self, now): self.now = now def __call__(self): return self.now def advance(self, seconds): self.now += seconds @pytest.fixture def clock(monkeypatch): # cachelib does ``from time import time``, so the module attribute has to # be replaced fake = _Clock(1_700_000_000.0) monkeypatch.setattr(cachelib.simple, "time", fake) monkeypatch.setattr(cachelib.file, "time", fake) return fake @pytest.fixture( params=[method for method in fsc.SUPPORTED_HASH_FUNCTIONS], ids=[method.__name__ for method in fsc.SUPPORTED_HASH_FUNCTIONS], ) def hash_method(request): return request.param def _server_is_running(port): with socket.socket() as sock: sock.settimeout(1) return sock.connect_ex(("127.0.0.1", port)) == 0 def _ensure_server(xprocess, name, executable, port, starter): if _server_is_running(port): yield return if os.environ.get("CI", "false") == "true": pytest.fail(f"no {executable} server listening on port {port}") if shutil.which(executable) is None: pytest.skip(f"could not find {executable} executable") try: xprocess.ensure(name, starter) except (OSError, RuntimeError) as exc: pytest.skip(f"could not start {executable}: {exc}") yield xprocess.getinfo(name).terminate() @pytest.fixture(scope="class") def redis_server(xprocess): package_name = "redis" pytest.importorskip( modname=package_name, reason=f"could not find python package {package_name}" ) class Starter(ProcessStarter): timeout = 20 pattern = "[Rr]eady to accept connections" args = ["redis-server", "--port 6360"] def startup_check(self): out = subprocess.run( ["redis-cli", "-p", "6360", "ping"], stdout=subprocess.PIPE ) return out.stdout == b"PONG\n" yield from _ensure_server(xprocess, package_name, "redis-server", 6360, Starter) @pytest.fixture(scope="class") def memcache_server(xprocess): package_name = "pylibmc" pytest.importorskip( modname=package_name, reason=f"could not find python package {package_name}" ) class Starter(ProcessStarter): timeout = 20 pattern = "server listening" args = ["memcached", "-vv", "-p", "11212"] def startup_check(self): out = subprocess.run(["memcached", "-p", "11212"], stderr=subprocess.PIPE) return b"Address already" in out.stderr yield from _ensure_server(xprocess, package_name, "memcached", 11212, Starter) pallets-eco-flask-caching-93fd601/tests/test_backend_cache.py000066400000000000000000000217571524661224300242560ustar00rootroot00000000000000""" tests.cache ~~~~~~~~~~~ Tests the cache system :copyright: (c) 2014 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import pickle import time import pytest from flask_caching import backends from flask_caching.backends import RedisSentinelCache try: import redis except ImportError: redis = None try: import pylibmc as memcache except ImportError: try: from google.appengine.api import memcache except ImportError: try: import memcache except ImportError: memcache = None class CacheTestsBase: _can_use_fast_sleep = True _guaranteed_deletes = True @pytest.fixture def make_cache(self): """Return a cache class or factory.""" raise NotImplementedError() @pytest.fixture def c(self, make_cache): """Return a cache instance.""" return make_cache() @pytest.fixture def sleep(self, clock): """Stay a while and listen...""" return clock.advance if self._can_use_fast_sleep else time.sleep class GenericCacheTests(CacheTestsBase): def test_generic_get_dict(self, c): assert c.set("a", "a") assert c.set("b", "b") d = c.get_dict("a", "b") assert "a" in d assert "a" == d["a"] assert "b" in d assert "b" == d["b"] def test_generic_set_get(self, c): for i in range(3): assert c.set(str(i), i * i) for i in range(3): result = c.get(str(i)) assert result == i * i, result def test_generic_get_set(self, c): assert c.set("foo", ["bar"]) assert c.get("foo") == ["bar"] def test_generic_get_many(self, c): assert c.set("foo", ["bar"]) assert c.set("spam", "eggs") assert c.get_many("foo", "spam") == [["bar"], "eggs"] def test_generic_set_many(self, c): assert c.set_many({"foo": "bar", "spam": ["eggs"]}) assert c.get("foo") == "bar" assert c.get("spam") == ["eggs"] def test_generic_add(self, c): # sanity check that add() works like set() assert c.add("foo", "bar") assert c.get("foo") == "bar" assert not c.add("foo", "qux") assert c.get("foo") == "bar" def test_generic_delete(self, c): assert c.add("foo", "bar") assert c.get("foo") == "bar" assert c.delete("foo") assert c.get("foo") is None def test_generic_delete_many(self, c): assert c.add("foo", "bar") assert c.add("spam", "eggs") assert c.delete_many("foo", "spam") assert c.get("foo") is None assert c.get("spam") is None def test_generic_inc_dec(self, c): assert c.set("foo", 1) assert c.inc("foo") == c.get("foo") == 2 assert c.dec("foo") == c.get("foo") == 1 assert c.delete("foo") def test_generic_true_false(self, c): assert c.set("foo", True) assert c.get("foo") in (True, 1) assert c.set("bar", False) assert c.get("bar") in (False, 0) def test_generic_timeout(self, c, sleep): c.set("foo", "bar", 0) assert c.get("foo") == "bar" c.set("baz", "qux", 1) assert c.get("baz") == "qux" sleep(3) # timeout of zero means no timeout assert c.get("foo") == "bar" if self._guaranteed_deletes: assert c.get("baz") is None def test_generic_has(self, c): assert c.has("foo") in (False, 0) assert c.has("spam") in (False, 0) assert c.set("foo", "bar") assert c.has("foo") in (True, 1) assert c.has("spam") in (False, 0) c.delete("foo") assert c.has("foo") in (False, 0) assert c.has("spam") in (False, 0) def test_generic_get_bytes(self, c): assert c.set("foo", b"bar") assert c.get("foo") == b"bar" class TestSimpleCache(GenericCacheTests): @pytest.fixture def make_cache(self): return backends.SimpleCache def test_purge(self): c = backends.SimpleCache(threshold=2) c.set("a", "a") c.set("b", "b") c.set("c", "c") c.set("d", "d") # Cache purges old items *before* it sets new ones. assert len(c._cache) == 3 def test_deprecated_ignore_errors(self): with pytest.warns(DeprecationWarning, match="ignore_errors"): c = backends.SimpleCache(ignore_errors=True) assert c.ignore_delete_many_errors is True def test_ignore_delete_many_errors(self): c = backends.SimpleCache(ignore_delete_many_errors=True) assert c.ignore_delete_many_errors is True class TestFileSystemCache(GenericCacheTests): @pytest.fixture def make_cache(self, tmpdir): return lambda **kw: backends.FileSystemCache(cache_dir=str(tmpdir), **kw) def test_deprecated_ignore_errors(self, tmpdir): with pytest.warns(DeprecationWarning, match="ignore_errors"): c = backends.FileSystemCache(cache_dir=str(tmpdir), ignore_errors=True) assert c.ignore_delete_many_errors is True def test_ignore_delete_many_errors(self, tmpdir): c = backends.FileSystemCache( cache_dir=str(tmpdir), ignore_delete_many_errors=True ) assert c.ignore_delete_many_errors is True # don't use pytest.mark.skipif on subclasses # https://bitbucket.org/hpk42/pytest/issue/568 # skip happens in requirements fixture instead class TestRedisCache(GenericCacheTests): _can_use_fast_sleep = False def gen_key_prefix(self): return "werkzeug-test-case:" @pytest.fixture(scope="class", autouse=True) @classmethod def requirements(cls, redis_server): pass @pytest.fixture(params=(None, False, True, gen_key_prefix)) def make_cache(self, request): key_prefix = "werkzeug-test-case:" if request.param is None: host = "localhost" elif request.param: host = redis.StrictRedis(port=6360) elif callable(request.param): key_prefix = gen_key_prefix # noqa (flake8 error: undefined) host = redis.Redis(port=6360) else: host = redis.Redis(port=6360) c = backends.RedisCache(host=host, key_prefix=key_prefix, port=6360) yield lambda: c c.clear() def test_compat(self, c): assert c._write_client.set(c.key_prefix + "foo", "Awesome") assert c.get("foo") == b"Awesome" assert c._write_client.set(c.key_prefix + "foo", "42") assert c.get("foo") == 42 def test_empty_host(self): with pytest.raises(ValueError) as exc_info: backends.RedisCache(host=None) assert str(exc_info.value) == "RedisCache host parameter may not be None" class TestRedisCacheClientsOverride(CacheTestsBase): _can_use_fast_sleep = False @pytest.fixture() def make_cache(self, request): c = RedisSentinelCache() yield lambda: c def test_client_override_reflected_on_cachelib_methods(self, c): EXPECTED_GET_MANY_VALUES = ["bacon", "spam", "eggs"] class DummyWriteClient: # cachelib < 0.15 uses setex(), >= 0.15 uses set(ex=...) def set(self, *args, **kwargs): return "spam" def setex(self, *args, **kwargs): return "spam" class DummyReadClient: def mget(self, *args, **kwargs): values = [ b"!" + pickle.dumps(v, pickle.HIGHEST_PROTOCOL) for v in EXPECTED_GET_MANY_VALUES ] return values c._write_client = DummyWriteClient() c._read_client = DummyReadClient() actual_values = c.get_many("foo") assert c.set("bacon", "eggs") == "spam" for actual, expected in zip( actual_values, EXPECTED_GET_MANY_VALUES, strict=False ): assert actual == expected class TestMemcachedCache(GenericCacheTests): _can_use_fast_sleep = False _guaranteed_deletes = False @pytest.fixture(scope="class", autouse=True) @classmethod def requirements(cls, memcache_server): pass @pytest.fixture def make_cache(self): c = backends.MemcachedCache( servers=["127.0.0.1:11212"], key_prefix="werkzeug-test-case:" ) yield lambda: c c.clear() def test_compat(self, c): assert c.set("foo", "bar") assert c.get("foo") == "bar" def test_huge_timeouts(self, c): # Timeouts greater than epoch are interpreted as POSIX timestamps # (i.e. not relative to now, but relative to epoch) epoch = 2592000 c.set("foo", "bar", epoch + 100) assert c.get("foo") == "bar" def test_timeouts(self, c): c.set("foo", "bar", 1) assert c.get("foo") == "bar" time.sleep(3) assert c.has("foo") is False class TestNullCache(CacheTestsBase): @pytest.fixture(scope="class", autouse=True) @classmethod def make_cache(cls): return backends.NullCache def test_has(self, c): assert not c.has("foo") pallets-eco-flask-caching-93fd601/tests/test_basic_app.py000066400000000000000000000077211524661224300234600ustar00rootroot00000000000000import pytest from flask import Flask from flask_caching import Cache from flask_caching.backends.simplecache import SimpleCache try: import redis # noqa HAS_NOT_REDIS = False except ImportError: HAS_NOT_REDIS = True class CustomCache(Cache): pass class CustomSimpleCache(SimpleCache): pass def newsimple(app, config, args, kwargs): return CustomSimpleCache(*args, **kwargs) def test_dict_config(app): cache = Cache(config={"CACHE_TYPE": "SimpleCache"}) cache.init_app(app) assert cache.config["CACHE_TYPE"] == "SimpleCache" def test_dict_config_initapp(app): cache = Cache() cache.init_app(app, config={"CACHE_TYPE": "SimpleCache"}) from flask_caching.backends.simplecache import SimpleCache assert isinstance(app.extensions["cache"][cache], SimpleCache) def test_dict_config_both(app): cache = Cache(config={"CACHE_TYPE": "NullCache"}) cache.init_app(app, config={"CACHE_TYPE": "SimpleCache"}) from flask_caching.backends.simplecache import SimpleCache assert isinstance(app.extensions["cache"][cache], SimpleCache) def test_init_app_sets_app_attribute(app): cache = Cache() cache.init_app(app) assert cache.app == app @pytest.mark.skipif(HAS_NOT_REDIS, reason="requires Redis") def test_init_app_multi_apps(app, redis_server): cache = Cache() app1 = Flask(__name__) app1.config.from_mapping( { "CACHE_TYPE": "RedisCache", "CACHE_KEY_PREFIX": "foo", "CACHE_REDIS_PORT": 6360, } ) app2 = Flask(__name__) app2.config.from_mapping( { "CACHE_TYPE": "RedisCache", "CACHE_KEY_PREFIX": "bar", "CACHE_REDIS_PORT": 6360, } ) cache.init_app(app1) cache.init_app(app2) # When we have the app context, the prefix should be # different for each app. with app1.app_context(): assert cache.cache.key_prefix == "foo" with app2.app_context(): assert cache.cache.key_prefix == "bar" @pytest.mark.skipif(HAS_NOT_REDIS, reason="requires Redis") def test_app_redis_cache_backend_url_default_db(app, redis_server): config = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6360", } cache = Cache() cache.init_app(app, config=config) from flask_caching.backends.rediscache import RedisCache assert isinstance(app.extensions["cache"][cache], RedisCache) rconn = app.extensions["cache"][ cache ]._write_client.connection_pool.get_connection() assert rconn.db == 0 @pytest.mark.skipif(HAS_NOT_REDIS, reason="requires Redis") def test_app_redis_cache_backend_url_custom_db(app, redis_server): config = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6360/2", } cache = Cache() cache.init_app(app, config=config) rconn = app.extensions["cache"][ cache ]._write_client.connection_pool.get_connection() assert rconn.db == 2 @pytest.mark.skipif(HAS_NOT_REDIS, reason="requires Redis") def test_app_redis_cache_backend_url_explicit_db_arg(app, redis_server): config = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_URL": "redis://localhost:6360", "CACHE_REDIS_DB": 1, } cache = Cache() cache.init_app(app, config=config) rconn = app.extensions["cache"][ cache ]._write_client.connection_pool.get_connection() assert rconn.db == 1 def test_app_custom_cache_backend(app): cache = Cache() app.config["CACHE_TYPE"] = "test_basic_app.newsimple" cache.init_app(app) with app.app_context(): assert isinstance(cache.cache, CustomSimpleCache) def test_subclassed_cache_class(app): # just invoking it here proofs that everything worked when subclassing # otherwise an werkzeug.utils.ImportStringError exception will be raised # because flask-caching can't find the backend # testing for "not raises" looked more hacky like this.. CustomCache(app) pallets-eco-flask-caching-93fd601/tests/test_cache.py000066400000000000000000000317271524661224300226050ustar00rootroot00000000000000import random import time import flask import pytest from flask_caching import Cache try: import redis # noqa HAS_NOT_REDIS = False except ImportError: HAS_NOT_REDIS = True def test_cache_set(app, cache): cache.set("hi", "hello") assert cache.get("hi") == "hello" def test_cache_has(app, cache): cache.add("hi", "hello") assert cache.has("hi") def test_cache_add(app, cache): cache.add("hi", "hello") assert cache.get("hi") == "hello" cache.add("hi", "foobar") assert cache.get("hi") == "hello" def test_cache_delete(app, cache): cache.set("hi", "hello") cache.delete("hi") assert cache.get("hi") is None def test_cache_delete_many(app, cache): cache.set("hi", "hello") # "ho" was never set, so it counts as deleted and "hi" is still removed. cache.delete_many("ho", "hi") assert cache.get("hi") is None @pytest.mark.skipif(HAS_NOT_REDIS, reason="requires Redis") def test_cache_unlink(app, redis_server): cache = Cache(config={"CACHE_TYPE": "RedisCache", "CACHE_REDIS_PORT": 6360}) cache.init_app(app) cache.set("biggerkey", "test" * 100) cache.unlink("biggerkey") assert cache.get("biggerkey") is None cache.set("biggerkey1", "test" * 100) cache.set("biggerkey2", "test" * 100) cache.unlink("biggerkey1", "biggerkey2") assert cache.get("biggerkey1") is None assert cache.get("biggerkey2") is None def test_cache_unlink_if_not(app): cache = Cache(config={"CACHE_TYPE": "SimpleCache"}) cache.init_app(app) cache.set("biggerkey", "test" * 100) cache.unlink("biggerkey") assert cache.get("biggerkey") is None cache.set("biggerkey1", "test" * 100) cache.set("biggerkey2", "test" * 100) cache.unlink("biggerkey1", "biggerkey2") assert cache.get("biggerkey1") is None assert cache.get("biggerkey2") is None def test_cache_delete_many_ignored(app): cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_IGNORE_ERRORS": True}) cache.init_app(app) cache.set("hi", "hello") assert cache.get("hi") == "hello" cache.delete_many("ho", "hi") assert cache.get("hi") is None def test_cache_cached_function(app, cache, clock): with app.test_request_context(): @cache.cached(1, key_prefix="MyBits") def get_random_bits(): return [random.randrange(0, 2) for i in range(50)] my_list = get_random_bits() his_list = get_random_bits() assert my_list == his_list clock.advance(2) his_list = get_random_bits() assert my_list != his_list def test_cache_cached_function_with_source_check_enabled(app, cache): with app.test_request_context(): @cache.cached(key_prefix="MyBits", source_check=True) def get_random_bits(): return [random.randrange(0, 2) for i in range(50)] first_attempt = get_random_bits() second_attempt = get_random_bits() assert second_attempt == first_attempt # ... change the source to see if the return value changes when called @cache.cached(key_prefix="MyBits", source_check=True) def get_random_bits(): return {"val": [random.randrange(0, 2) for i in range(50)]} third_attempt = get_random_bits() assert third_attempt != first_attempt # We changed the return data type so we do a check to be sure assert isinstance(third_attempt, dict) # ... change the source back to what it was original and the data should # be the same @cache.cached(key_prefix="MyBits", source_check=True) def get_random_bits(): return [random.randrange(0, 2) for i in range(50)] forth_attempt = get_random_bits() assert forth_attempt == first_attempt def test_cache_cached_function_with_source_check_disabled(app, cache): with app.test_request_context(): @cache.cached(key_prefix="MyBits", source_check=False) def get_random_bits(): return [random.randrange(0, 2) for i in range(50)] first_attempt = get_random_bits() second_attempt = get_random_bits() assert second_attempt == first_attempt # ... change the source to see if the return value changes when called @cache.cached(key_prefix="MyBits", source_check=False) def get_random_bits(): return {"val": [random.randrange(0, 2) for i in range(50)]} third_attempt = get_random_bits() assert third_attempt == first_attempt def test_cache_accepts_multiple_ciphers(app, cache, hash_method, clock): with app.test_request_context(): @cache.cached(1, key_prefix="MyBits", hash_method=hash_method) def get_random_bits(): return [random.randrange(0, 2) for i in range(50)] my_list = get_random_bits() his_list = get_random_bits() assert my_list == his_list clock.advance(2) his_list = get_random_bits() assert my_list != his_list def test_cached_none(app, cache): with app.test_request_context(): from collections import Counter call_counter = Counter() @cache.cached(cache_none=True) def cache_none(param): call_counter[param] += 1 return None cache_none(1) assert call_counter[1] == 1 assert cache_none(1) is None assert call_counter[1] == 1 cache.clear() cache_none(1) assert call_counter[1] == 2 def test_cached_doesnt_cache_none(app, cache): """Asserting that when cache_none is False, we always assume a None value returned from .get() means the key is not found """ with app.test_request_context(): from collections import Counter call_counter = Counter() @cache.cached() def cache_none(param): call_counter[param] += 1 return None cache_none(1) # The cached function should have been called assert call_counter[1] == 1 # Next time we call the function, the value should be coming from the cache… # But the value is None and so we treat it as uncached. assert cache_none(1) is None # …thus, the call counter should increment to 2 assert call_counter[1] == 2 cache.clear() cache_none(1) assert call_counter[1] == 3 def test_cache_forced_update(app, cache): from collections import Counter with app.test_request_context(): need_update = False call_counter = Counter() @cache.cached(1, forced_update=lambda: need_update) def cached_function(param): call_counter[param] += 1 return 1 cached_function(1) assert call_counter[1] == 1 assert cached_function(1) == 1 assert call_counter[1] == 1 need_update = True assert cached_function(1) == 1 assert call_counter[1] == 2 def test_cache_forced_update_params(app, cache): from collections import Counter with app.test_request_context(): cached_call_counter = Counter() call_counter = Counter() call_params = {} def need_update(param): """This helper function returns True if it has been called with the same params for more than 2 times """ call_counter[param] += 1 call_params[call_counter[param] - 1] = (param,) return call_counter[param] > 2 @cache.cached(1, forced_update=need_update) def cached_function(param): cached_call_counter[param] += 1 return 1 assert cached_function(1) == 1 # need_update should have been called once assert call_counter[1] == 1 # the parameters used to call need_update should be the same as the # parameters used to call cached_function assert call_params[0] == (1,) # the cached function should have been called once assert cached_call_counter[1] == 1 assert cached_function(1) == 1 # need_update should have been called twice by now as forced_update # should be called regardless of the arguments assert call_counter[1] == 2 # the parameters used to call need_update should be the same as the # parameters used to call cached_function assert call_params[1] == (1,) # this time the forced_update should have returned False, so # cached_function should not have been called again assert cached_call_counter[1] == 1 assert cached_function(1) == 1 # need_update should have been called thrice by now as forced_update # should be called regardless of the arguments assert call_counter[1] == 3 # the parameters used to call need_update should be the same as the # parameters used to call cached_function assert call_params[1] == (1,) # this time the forced_update should have returned True, so # cached_function should have been called again assert cached_call_counter[1] == 2 def test_cache_is_stale(app, cache): from collections import Counter with app.test_request_context(): call_counter = Counter() cached_values = [] def is_stale(value): cached_values.append(value) return value < 2 @cache.cached(5, key_prefix="is_stale", is_stale=is_stale) def cached_function(): call_counter[1] += 1 return call_counter[1] assert cached_function() == 1 # is_stale is only accessed on a cache hit assert cached_values == [] # the cached 1 is stale so the function runs again and caches 2 assert cached_function() == 2 assert cached_values == [1] assert call_counter[1] == 2 # 2 is not stale so the cached value is returned as is assert cached_function() == 2 assert cached_values == [1, 2] assert call_counter[1] == 2 def test_cache_is_stale_params(app, cache): with app.test_request_context(): call_params = [] def is_stale(value, param): call_params.append((value, param)) return False @cache.cached(5, key_prefix="is_stale_params", is_stale=is_stale) def cached_function(param): return param assert cached_function(1) == 1 assert call_params == [] assert cached_function(1) == 1 # the cached value comes first followed by the calls own arguments assert call_params == [(1, 1)] def test_cache_is_stale_not_called_on_forced_update(app, cache): with app.test_request_context(): called = [] @cache.cached( 5, key_prefix="is_stale_forced", forced_update=lambda: True, is_stale=lambda value: called.append(value) or True, ) def cached_function(): return 1 cached_function() cached_function() assert called == [] def test_generator(app, cache): """test function return generator""" with app.test_request_context(): @cache.cached() def gen(): return (str(time.time()) for i in range(2)) time_str = gen() assert gen() == time_str @cache.cached() def gen_yield(): yield str(time.time()) yield str(time.time()) time_str = gen_yield() assert gen_yield() == time_str def test_generator_of_strings_is_joined(app, cache): """A generator of strings is cached as one string, not a list.""" with app.test_request_context(): @cache.cached() def gen(): yield "

" yield "hello" yield "

" assert gen() == "

hello

" assert gen() == "

hello

" def test_generator_of_bytes_is_joined(app, cache): with app.test_request_context(): @cache.cached() def gen(): yield b"

" yield b"hello" yield b"

" assert gen() == b"

hello

" assert gen() == b"

hello

" def test_generator_of_other_types_stays_a_list(app, cache): with app.test_request_context(): @cache.cached() def gen(): yield 1 yield 2 assert gen() == [1, 2] assert gen() == [1, 2] def test_cached_view_returning_stream_template(app, cache): """See issue #511, a streamed view must not be cached as a JSON array.""" @app.route("/stream") @cache.cached() def stream(): return flask.stream_template("test_stream.html", somevar="hello") client = app.test_client() for _ in range(2): response = client.get("/stream") assert response.content_type == "text/html; charset=utf-8" assert response.get_data(as_text=True) == "hello" def test_memoized_generator_of_strings_is_joined(app, cache): @cache.memoize() def gen(prefix): yield prefix yield "hello" assert gen("a") == "ahello" assert gen("a") == "ahello" assert gen("b") == "bhello" pallets-eco-flask-caching-93fd601/tests/test_cachelib.py000066400000000000000000000074731524661224300232750ustar00rootroot00000000000000"""Tests for the cachelib integration. Flask-Caching runs on top of cachelib: the built-in backends subclass their cachelib counterparts, and ``CACHE_TYPE`` may also point straight at a cachelib class, in which case the class is instantiated directly with ``CACHE_ARGS``/``CACHE_OPTIONS`` instead of going through :meth:`.BaseCache.factory`. """ import cachelib import pytest from cachelib.serializers import SimpleSerializer from flask_caching import Cache @pytest.mark.parametrize( "cache_type", ("cachelib.SimpleCache", "cachelib.simple.SimpleCache"), ) def test_cachelib_class_as_cache_type(app, cache_type): """A cachelib class is used as-is, not wrapped in a subclass.""" app.config["CACHE_TYPE"] = cache_type cache = Cache(app) backend = app.extensions["cache"][cache] assert type(backend) is cachelib.SimpleCache def test_cachelib_backend_gets_default_timeout(app): app.config["CACHE_TYPE"] = "cachelib.SimpleCache" app.config["CACHE_DEFAULT_TIMEOUT"] = 111 cache = Cache(app) assert app.extensions["cache"][cache].default_timeout == 111 def test_cachelib_backend_gets_cache_options(app): """CACHE_OPTIONS are passed as keyword arguments to the cachelib class.""" app.config["CACHE_TYPE"] = "cachelib.SimpleCache" app.config["CACHE_OPTIONS"] = {"threshold": 7} cache = Cache(app) assert app.extensions["cache"][cache]._threshold == 7 def test_cachelib_backend_gets_cache_args(app, tmp_path): """CACHE_ARGS are passed as positional arguments to the cachelib class.""" app.config["CACHE_TYPE"] = "cachelib.FileSystemCache" app.config["CACHE_ARGS"] = [str(tmp_path)] cache = Cache(app) backend = app.extensions["cache"][cache] assert type(backend) is cachelib.FileSystemCache assert backend._path == str(tmp_path) def test_cachelib_backend_proxy_methods(app): app.config["CACHE_TYPE"] = "cachelib.SimpleCache" cache = Cache(app) with app.app_context(): cache.set("hi", "hello") assert cache.get("hi") == "hello" assert cache.has("hi") cache.add("hi", "foobar") assert cache.get("hi") == "hello" cache.set_many({"a": 1, "b": 2}) assert cache.get_many("a", "b") == [1, 2] cache.delete("hi") assert cache.get("hi") is None cache.clear() assert cache.get("a") is None def test_cachelib_backend_with_cached_view(app): app.config["CACHE_TYPE"] = "cachelib.SimpleCache" cache = Cache(app) calls = [] @app.route("/") @cache.cached() def view(): calls.append(1) return "ok" client = app.test_client() assert client.get("/").data == b"ok" assert client.get("/").data == b"ok" assert len(calls) == 1 def test_cachelib_backend_with_memoize(app): app.config["CACHE_TYPE"] = "cachelib.SimpleCache" cache = Cache(app) calls = [] @cache.memoize() def double(x): calls.append(x) return x * 2 with app.app_context(): assert double(2) == 4 assert double(2) == 4 assert len(calls) == 1 cache.delete_memoized(double) assert double(2) == 4 assert len(calls) == 2 def test_backend_uses_cachelib_serializer(app): """The backend holds a cachelib serializer that can be replaced.""" cache = Cache(app) backend = app.extensions["cache"][cache] assert isinstance(backend.serializer, SimpleSerializer) class CountingSerializer(SimpleSerializer): def __init__(self): self.dumped = 0 def dumps(self, value, protocol=None): self.dumped += 1 return super().dumps(value) serializer = CountingSerializer() backend.serializer = serializer with app.app_context(): cache.set("hi", "hello") assert cache.get("hi") == "hello" assert serializer.dumped == 1 pallets-eco-flask-caching-93fd601/tests/test_hash_method.py000066400000000000000000000111471524661224300240170ustar00rootroot00000000000000import hashlib import random import pytest from flask_caching import Cache def _query_string_key(path, args_as_sorted_tuple, hash_method, key_prefix="view/%s"): """Rebuild the key that ``cached(query_string=True)`` produces.""" return (key_prefix % path) + hash_method( str(args_as_sorted_tuple).encode() ).hexdigest() def test_default_hash_method_is_sha256(app): cache = Cache(app) @app.route("/works") @cache.cached(query_string=True) def view(): return "value" with app.test_request_context("/works?a=1&b=2"): assert view.make_cache_key() == _query_string_key( "/works", (("a", "1"), ("b", "2")), hashlib.sha256 ) def test_query_string_key_includes_key_prefix(app, cache): @app.route("/works") @cache.cached(query_string=True, key_prefix="custom/%s") def view(): return "value" with app.test_request_context("/works?a=1"): assert view.make_cache_key() == _query_string_key( "/works", (("a", "1"),), hashlib.sha256, key_prefix="custom/%s" ) def test_cached_uses_configured_hash_method(app): app.config["CACHE_HASH_METHOD"] = hashlib.md5 cache = Cache(app) @app.route("/works") @cache.cached(query_string=True) def view(): return "value" with app.test_request_context("/works?a=1&b=2"): assert view.make_cache_key() == _query_string_key( "/works", (("a", "1"), ("b", "2")), hashlib.md5 ) def test_decorator_argument_overrides_configured_hash_method(app): app.config["CACHE_HASH_METHOD"] = hashlib.md5 cache = Cache(app) @app.route("/works") @cache.cached(query_string=True, hash_method=hashlib.sha512) def view(): return "value" with app.test_request_context("/works?a=1&b=2"): assert view.make_cache_key() == _query_string_key( "/works", (("a", "1"), ("b", "2")), hashlib.sha512 ) def test_hash_method_read_from_extension_config(app): """The option also works when passed to ``Cache`` instead of ``app``.""" cache = Cache(app, config={"CACHE_HASH_METHOD": hashlib.md5}) @app.route("/works") @cache.cached(query_string=True) def view(): return "value" with app.test_request_context("/works?a=1&b=2"): assert view.make_cache_key() == _query_string_key( "/works", (("a", "1"), ("b", "2")), hashlib.md5 ) def test_non_callable_hash_method_raises(app): app.config["CACHE_HASH_METHOD"] = "sha256" with pytest.raises(ValueError, match="CACHE_HASH_METHOD"): Cache(app) def test_hash_method_resolved_after_decoration(app): """Decorators run at import time, before ``init_app``. The configured hash method must therefore be picked up when the key is built, not when the decorator is applied. """ cache = Cache() @cache.cached(query_string=True) def view(): return "value" app.config["CACHE_HASH_METHOD"] = hashlib.md5 cache.init_app(app) with app.test_request_context("/works?a=1&b=2"): assert view.make_cache_key() == _query_string_key( "/works", (("a", "1"), ("b", "2")), hashlib.md5 ) def test_delete_memoized_matches_configured_hash_method(app): """``make_cache_key`` is built at decoration time and must agree with the key used at call time, otherwise ``delete_memoized`` deletes nothing.""" app.config["CACHE_HASH_METHOD"] = hashlib.md5 cache = Cache(app) @cache.memoize(50) def add(a, b): return a + b + random.random() with app.test_request_context(): result = add(1, 2) assert add(1, 2) == result cache.delete_memoized(add, 1, 2) assert add(1, 2) != result def test_configured_hash_method_changes_memoize_key(app): app.config["CACHE_HASH_METHOD"] = hashlib.md5 md5_cache = Cache(app) @md5_cache.memoize(50) def add(a, b): return a + b with app.test_request_context(): md5_key = add.make_cache_key(add.uncached, 1, 2) # Swap the configured method on the same cache so that the version # hash stays identical and only the digest can differ. md5_cache.hash_method = hashlib.sha512 assert add.make_cache_key(add.uncached, 1, 2) != md5_key def test_file_hash_method_is_independent(app, tmp_path): """``CACHE_HASH_METHOD`` must not change the FileSystemCache file names.""" app.config.update( CACHE_TYPE="FileSystemCache", CACHE_DIR=str(tmp_path), CACHE_HASH_METHOD=hashlib.md5, ) cache = Cache(app) with app.test_request_context(): assert cache.cache._hash_method is hashlib.sha256 pallets-eco-flask-caching-93fd601/tests/test_init.py000066400000000000000000000065671524661224300225110ustar00rootroot00000000000000import importlib.util import inspect import pytest from flask import Flask from flask import make_response from flask_caching import Cache from flask_caching import CachedResponse from flask_caching.backends import FileSystemCache from flask_caching.backends import MemcachedCache from flask_caching.backends import NullCache from flask_caching.backends import RedisCache from flask_caching.backends import RedisSentinelCache from flask_caching.backends import SASLMemcachedCache from flask_caching.backends import SimpleCache from flask_caching.backends import SpreadSASLMemcachedCache # pylibmc doesn't have any wheels for python > 3.11 pylibmc_available = False try: importlib.util.find_spec("pylibmc") except ImportError: pylibmc_available = False cache_types = [ FileSystemCache, MemcachedCache, NullCache, RedisCache, RedisSentinelCache, SimpleCache, ] if pylibmc_available: cache_types.append(SASLMemcachedCache) cache_types.append(SpreadSASLMemcachedCache) @pytest.fixture def app(): app_ = Flask(__name__) return app_ @pytest.mark.parametrize( "cache_type", (cache_types), ) def test_init_nullcache(cache_type, app, tmp_path): extra_config = { FileSystemCache: { "CACHE_DIR": tmp_path, }, MemcachedCache: { "CACHE_MEMCACHED_SERVERS": ["127.0.0.1:11212"], }, RedisCache: { "CACHE_REDIS_PORT": 6360, }, SASLMemcachedCache: { "CACHE_MEMCACHED_SERVERS": ["127.0.0.1:11212"], "CACHE_MEMCACHED_USERNAME": "test", "CACHE_MEMCACHED_PASSWORD": "test", }, SpreadSASLMemcachedCache: { "CACHE_MEMCACHED_SERVERS": ["127.0.0.1:11212"], "CACHE_MEMCACHED_USERNAME": "test", "CACHE_MEMCACHED_PASSWORD": "test", }, } app.config["CACHE_TYPE"] = "flask_caching.backends." + cache_type.__name__ app.config.update(extra_config.get(cache_type, {})) cache = Cache(app=app) assert isinstance(app.extensions["cache"][cache], cache_type) def test_cached_response_init_is_typed(): """Regression test for issue #628. ``CachedResponse.__init__`` must declare a return type and parameter types so that mypy ``--strict`` doesn't flag construction in typed code with ``[no-untyped-call]``. See https://github.com/pallets-eco/flask-caching/issues/628. """ sig = inspect.signature(CachedResponse.__init__) # __init__ should declare ``-> None`` rather than be untyped. # inspect.signature returns the literal ``None`` for ``-> None``. assert sig.return_annotation is None, ( f"CachedResponse.__init__ return annotation is {sig.return_annotation!r}; " "expected None" ) # All non-self parameters should carry an annotation. for name, param in sig.parameters.items(): if name == "self": continue assert param.annotation is not inspect.Parameter.empty, ( f"CachedResponse.__init__ parameter {name!r} has no annotation" ) def test_cached_response_construction_with_flask_response(): """Sanity check that CachedResponse still wraps a flask Response.""" app = Flask(__name__) with app.test_request_context(): resp = make_response("hi") cached = CachedResponse(resp, 10) assert cached.timeout == 10 assert cached.get_data(as_text=True) == "hi" pallets-eco-flask-caching-93fd601/tests/test_memoize.py000066400000000000000000000772151524661224300232110ustar00rootroot00000000000000import random import time import pytest from flask_caching import Cache from flask_caching import function_namespace def test_memoize(app, cache, clock): with app.test_request_context(): @cache.memoize(3) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) clock.advance(1) assert big_foo(5, 2) == result result2 = big_foo(5, 3) assert result2 != result clock.advance(3) assert big_foo(5, 2) != result clock.advance(1) assert big_foo(5, 3) != result2 def test_memoize_falsy_default_argument(app, cache): with app.test_request_context(): runs = [] @cache.memoize() def f(a, b=0): runs.append((a, b)) return f"{a}:{b}" # A falsy default (0) must not collide with an explicit None. assert f(1, None) == "1:None" assert f(1) == "1:0" assert f(1, 0) == "1:0" assert f(1, b=0) == "1:0" # None and the default 0 are distinct; the three b=0 forms share a key. assert runs == [(1, None), (1, 0)] def test_memoize_hashes(app, cache, hash_method, clock): with app.test_request_context(): @cache.memoize(3, hash_method=hash_method) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) clock.advance(1) assert big_foo(5, 2) == result result2 = big_foo(5, 3) assert result2 != result clock.advance(3) assert big_foo(5, 2) != result clock.advance(1) assert big_foo(5, 3) != result2 def test_memoize_timeout(app, clock): app.config["CACHE_DEFAULT_TIMEOUT"] = 1 cache = Cache(app) with app.test_request_context(): @cache.memoize() def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) assert big_foo(5, 2) == result clock.advance(2) assert big_foo(5, 2) != result def test_memoize_entry_written_late_keeps_its_own_timeout(app, cache, clock): with app.test_request_context(): runs = [] @cache.memoize(timeout=60) def f(param): runs.append(param) return param.upper() assert f("icecream") == "ICECREAM" clock.advance(59) assert f("waffles") == "WAFFLES" # t=61: the version key used to expire here and orphan both entries. clock.advance(2) assert f("waffles") == "WAFFLES" assert runs == ["icecream", "waffles"] # t=120: past this entry's own timeout, so it expires normally. clock.advance(59) assert f("waffles") == "WAFFLES" assert runs == ["icecream", "waffles", "waffles"] def test_memoize_version_key_outlives_newest_entry(app, cache, clock): with app.test_request_context(): @cache.memoize(timeout=60) def f(param): return param.upper() version_key = cache._memvname(function_namespace(f.uncached)[0]) f("a") clock.advance(50) # t=50: this entry expires at t=110, so the version key has to as well. f("b") clock.advance(59) # t=109: entry "b" still has a second left, so its version must be live. assert cache.get(version_key) is not None def test_memoize_version_key_expires_once_its_entries_have(app, cache, clock): with app.test_request_context(): @cache.memoize(timeout=60) def f(param): return param.upper() f("a") version_key = cache._memvname(function_namespace(f.uncached)[0]) assert cache.get(version_key) is not None clock.advance(61) assert cache.get(version_key) is None def test_memoize_delete_resets_version_key(app, cache, clock): with app.test_request_context(): runs = [] @cache.memoize(timeout=60) def f(param): runs.append(param) return param.upper() f("a") assert runs == ["a"] cache.delete_memoized(f) assert f("a") == "A" assert runs == ["a", "a"] clock.advance(3600) f("b") clock.advance(30) assert f("b") == "B" assert runs == ["a", "a", "b"] def test_memoize_annotated(app, cache): with app.test_request_context(): @cache.memoize(50) def big_foo_annotated(a, b): return a + b + random.randrange(0, 100000) big_foo_annotated.__annotations__ = { "a": int, "b": int, "return": int, } result = big_foo_annotated(5, 2) assert big_foo_annotated(5, 2) == result def test_memoize_utf8_arguments(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b): return f"{a}-{b}" big_foo("æøå", "chars") def test_memoize_unicode_arguments(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b): return f"{a}-{b}" big_foo("æøå", "chars") def test_memoize_delete(app, cache): with app.test_request_context(): @cache.memoize(5) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) result2 = big_foo(5, 3) assert big_foo(5, 2) == result assert big_foo(5, 2) == result assert big_foo(5, 3) != result assert big_foo(5, 3) == result2 cache.delete_memoized(big_foo) assert big_foo(5, 2) != result assert big_foo(5, 3) != result2 def test_memoize_delete_via_attribute(app, cache): with app.test_request_context(): @cache.memoize(5) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) assert big_foo(5, 2) == result big_foo.delete_memoized() assert big_foo(5, 2) != result def test_memoize_classfunc_delete_via_attribute(app, cache): with app.test_request_context(): class Adder: def __init__(self, initial): self.initial = initial @cache.memoize(5) def add(self, b): return self.initial + b + random.random() adder1 = Adder(1) adder2 = Adder(2) a1 = adder1.add(3) a2 = adder2.add(3) assert adder1.add(3) == a1 assert adder2.add(3) == a2 # the attribute takes no arguments, so it resets the version for # every instance, not just adder1 adder1.add.delete_memoized() assert adder1.add(3) != a1 assert adder2.add(3) != a2 def test_memoize_no_timeout_delete(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b): return a + b + random.randrange(0, 100000) result_a = big_foo(5, 1) result_b = big_foo(5, 2) assert big_foo(5, 1) == result_a assert big_foo(5, 2) == result_b cache.delete_memoized(big_foo, 5, 2) assert big_foo(5, 1) == result_a assert big_foo(5, 2) != result_b # Cleanup bigfoo 5,1 5,2 or it might conflict with # following run if it also uses memecache cache.delete_memoized(big_foo, 5, 2) cache.delete_memoized(big_foo, 5, 1) def test_memoize_verhash_delete(app, cache): with app.test_request_context(): @cache.memoize(5) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) result2 = big_foo(5, 3) assert big_foo(5, 2) == result assert big_foo(5, 2) == result assert big_foo(5, 3) != result assert big_foo(5, 3) == result2 cache.delete_memoized_verhash(big_foo) _fname, _fname_instance = function_namespace(big_foo) version_key = cache._memvname(_fname) assert cache.get(version_key) is None assert big_foo(5, 2) != result assert big_foo(5, 3) != result2 assert cache.get(version_key) is not None def test_memoize_annotated_delete(app, cache): with app.test_request_context(): @cache.memoize(5) def big_foo_annotated(a, b): return a + b + random.randrange(0, 100000) big_foo_annotated.__annotations__ = {"a": int, "b": int, "return": int} result = big_foo_annotated(5, 2) result2 = big_foo_annotated(5, 3) assert big_foo_annotated(5, 2) == result assert big_foo_annotated(5, 2) == result assert big_foo_annotated(5, 3) != result assert big_foo_annotated(5, 3) == result2 cache.delete_memoized_verhash(big_foo_annotated) _fname, _fname_instance = function_namespace(big_foo_annotated) version_key = cache._memvname(_fname) assert cache.get(version_key) is None assert big_foo_annotated(5, 2) != result assert big_foo_annotated(5, 3) != result2 assert cache.get(version_key) is not None def test_memoize_args(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b): return sum(a) + sum(b) + random.randrange(0, 100000) result_a = big_foo([5, 3, 2], [1]) result_b = big_foo([3, 3], [3, 1]) assert big_foo([5, 3, 2], [1]) == result_a assert big_foo([3, 3], [3, 1]) == result_b cache.delete_memoized(big_foo, [5, 3, 2], [1]) assert big_foo([5, 3, 2], [1]) != result_a assert big_foo([3, 3], [3, 1]) == result_b # Cleanup bigfoo 5,1 5,2 or it might conflict with # following run if it also uses memecache cache.delete_memoized(big_foo, [5, 3, 2], [1]) cache.delete_memoized(big_foo, [3, 3], [1]) def test_memoize_kwargs(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b=None): return a + sum(b.values()) + random.randrange(0, 100000) result_a = big_foo(1, dict(one=1, two=2)) result_b = big_foo(5, dict(three=3, four=4)) assert big_foo(1, dict(one=1, two=2)) == result_a assert big_foo(5, dict(three=3, four=4)) == result_b cache.delete_memoized(big_foo, 1, dict(one=1, two=2)) assert big_foo(1, dict(one=1, two=2)) != result_a assert big_foo(5, dict(three=3, four=4)) == result_b def test_memoize_kwargonly(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a=None): if a is None: a = 0 return a + random.random() result_a = big_foo() result_b = big_foo(5) assert big_foo() == result_a assert big_foo() < 1 assert big_foo(5) == result_b assert big_foo(5) >= 5 and big_foo(5) < 6 def test_memoize_arg_kwarg(app, cache): with app.test_request_context(): @cache.memoize() def f(a, b, c=1): return a + b + c + random.randrange(0, 100000) assert f(1, 2) == f(1, 2, c=1) assert f(1, 2) == f(1, 2, 1) assert f(1, 2) == f(1, 2) assert f(1, 2, 3) != f(1, 2) with pytest.raises(TypeError): f(1) def test_memoize_arg_kwarg_var_keyword(app, cache): with app.test_request_context(): @cache.memoize() def f(a, b, c=1, **kwargs): return a + b + c + random.randrange(0, 100000) + sum(list(kwargs.values())) assert f(1, 2) == f(1, 2, c=1) assert f(1, 2) == f(1, 2, 1) assert f(1, 2) == f(1, 2) assert f(1, 2, d=5, e=8) == f(1, 2, e=8, d=5) assert f(1, b=2, c=3, d=5, e=8) == f(1, 2, e=8, d=5, b=2, c=3) assert f(1, 2, 3) != f(1, 2) assert f(1, 2, 3) != f(1, 2) with pytest.raises(TypeError): f(1) def test_memoize_classarg(app, cache): @cache.memoize() def bar(a): return a.value + random.random() class Adder: def __init__(self, value): self.value = value adder = Adder(15) adder2 = Adder(20) y = bar(adder) z = bar(adder2) assert y != z assert bar(adder) == y assert bar(adder) != z adder.value = 14 assert bar(adder) == y assert bar(adder) != z assert bar(adder) != bar(adder2) assert bar(adder2) == z def test_memoize_classfunc(app, cache): class Adder: def __init__(self, initial): self.initial = initial @cache.memoize() def add(self, b): return self.initial + b adder1 = Adder(1) adder2 = Adder(2) x = adder1.add(3) assert adder1.add(3) == x assert adder1.add(4) != x assert adder1.add(3) != adder2.add(3) def test_memoize_classfunc_repr(app, cache): class Adder: def __init__(self, initial): self.initial = initial @cache.memoize() def add(self, b): return self.initial + b + random.random() def __repr__(self): return "42" def __caching_id__(self): return self.initial adder1 = Adder(1) adder2 = Adder(2) x = adder1.add(3) assert adder1.add(3) == x assert adder1.add(4) != x assert adder1.add(3) != adder2.add(3) def test_memoize_classfunc_delete(app, cache): with app.test_request_context(): class Adder: def __init__(self, initial): self.initial = initial @cache.memoize() def add(self, b): return self.initial + b + random.random() adder1 = Adder(1) adder2 = Adder(2) a1 = adder1.add(3) a2 = adder2.add(3) assert a1 != a2 assert adder1.add(3) == a1 assert adder2.add(3) == a2 cache.delete_memoized(adder1.add) a3 = adder1.add(3) a4 = adder2.add(3) assert not a1 == a3 # self.assertNotEqual(a1, a3) assert a1 != a3 assert a2 == a4 # self.assertEqual(a2, a4) cache.delete_memoized(Adder.add) a5 = adder1.add(3) a6 = adder2.add(3) assert not a5 == a6 # self.assertNotEqual(a5, a6) assert not a3 == a5 # self.assertNotEqual(a3, a5) assert not a4 == a6 # self.assertNotEqual(a4, a6) def test_memoize_classfunc_delete_with_args(app, cache): """A bound method carries its own instance, so ``delete_memoized`` only needs the arguments that follow it.""" with app.test_request_context(): class Adder: @cache.memoize(5) def add(self, b): return b + random.random() adder1 = Adder() adder2 = Adder() a1 = adder1.add(3) a2 = adder1.add(4) a3 = adder2.add(3) cache.delete_memoized(adder1.add, 3) # only ``adder1.add(3)`` is gone assert adder1.add(3) != a1 assert adder1.add(4) == a2 assert adder2.add(3) == a3 def test_memoize_classfunc_delete_with_explicit_instance(app, cache): """Passing the instance explicitly keeps working, so that code written against the previous behaviour is not broken.""" with app.test_request_context(): class Adder: @cache.memoize(5) def add(self, b): return b + random.random() adder1 = Adder() adder2 = Adder() a1 = adder1.add(3) a2 = adder1.add(4) a3 = adder2.add(3) cache.delete_memoized(adder1.add, adder1, 3) assert adder1.add(3) != a1 assert adder1.add(4) == a2 assert adder2.add(3) == a3 # ... and so does reaching the method through the class a1 = adder1.add(3) cache.delete_memoized(Adder.add, adder1, 3) assert adder1.add(3) != a1 assert adder2.add(3) == a3 def test_memoize_classfunc_delete_with_args_to_ignore_self(app, cache): with app.test_request_context(): class Adder: @cache.memoize(5, args_to_ignore=["self"]) def add(self, b): return b + random.random() adder1 = Adder() adder2 = Adder() a1 = adder1.add(3) a2 = adder1.add(4) # ``self`` is not part of the key, so both instances share it assert adder2.add(3) == a1 cache.delete_memoized(adder1.add, 3) assert adder1.add(3) != a1 assert adder1.add(4) == a2 def test_memoize_classfunc_delete_with_kwargs_only(app, cache): with app.test_request_context(): class Adder: @cache.memoize(5) def add(self, b): return b + random.random() adder1 = Adder() a1 = adder1.add(3) cache.delete_memoized(adder1.add, b=3) assert adder1.add(3) != a1 # reached through the class there is no instance to fall back on with pytest.raises(ValueError): cache.delete_memoized(Adder.add, b=3) def test_memoize_classmethod_delete(app, cache): with app.test_request_context(): class Mock: @classmethod @cache.memoize(5) def big_foo(cls, a, b): return a + b + random.randrange(0, 100000) result = Mock.big_foo(5, 2) result2 = Mock.big_foo(5, 3) assert Mock.big_foo(5, 2) == result assert Mock.big_foo(5, 2) == result assert Mock.big_foo(5, 3) != result assert Mock.big_foo(5, 3) == result2 cache.delete_memoized(Mock.big_foo) assert Mock.big_foo(5, 2) != result assert Mock.big_foo(5, 3) != result2 def test_memoize_classmethod_delete_with_args(app, cache): with app.test_request_context(): class Mock: @classmethod @cache.memoize(5) def big_foo(cls, a, b): return a + b + random.randrange(0, 100000) result = Mock.big_foo(5, 2) result2 = Mock.big_foo(5, 3) assert Mock.big_foo(5, 2) == result assert Mock.big_foo(5, 2) == result assert Mock.big_foo(5, 3) != result assert Mock.big_foo(5, 3) == result2 with pytest.raises(ValueError): cache.delete_memoized(Mock.big_foo, 5, 2) assert Mock.big_foo(5, 2) == result assert Mock.big_foo(5, 3) == result2 cache.delete_memoized(Mock.big_foo, Mock, 5, 2) assert Mock.big_foo(5, 2) != result assert Mock.big_foo(5, 3) == result2 def test_memoize_forced_update(app, cache): with app.test_request_context(): forced_update = False @cache.memoize(5, forced_update=lambda: forced_update) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) assert big_foo(5, 2) == result forced_update = True new_result = big_foo(5, 2) assert new_result != result forced_update = False assert big_foo(5, 2) == new_result def test_memoize_forced_update_parameters(app, cache): from collections import Counter with app.test_request_context(): call_counter = Counter() call_params = {} forced_update = False def forced_update_func(a, b): call_counter[1] += 1 call_params[call_counter[1] - 1] = (a, b) return forced_update @cache.memoize(5, forced_update=forced_update_func) def memoized_func(a, b): return a + b + random.randrange(0, 100000) # Save the value for later inspection result = memoized_func(5, 2) # forced_update_func should have been called once by memoize itself. assert call_counter[1] == 1 # …with the values we called the function with assert call_params[0] == (5, 2) # Calling the function again should return the cached value assert memoized_func(5, 2) == result # forced_update_func should have been called one more time… assert call_counter[1] == 2 # …with the values we called the function with assert call_params[1] == (5, 2) # Tell forced_update_func to return True next time forced_update = True # Save the new result… new_result = memoized_func(5, 2) # …which, due to the random number in the function, should be different # from the old one assert new_result != result # forced_update_func should have been called one more time again… assert call_counter[1] == 3 # …with the values we called the function with assert call_params[2] == (5, 2) # Now stop forced updating again forced_update = False # The function should return the same value as it did last time assert memoized_func(5, 2) == new_result # forced_update_func should have been called one more time again… assert call_counter[1] == 4 # …with the values we called the function with assert call_params[3] == (5, 2) def test_memoize_is_stale(app, cache): with app.test_request_context(): stale = False cached_values = [] def is_stale(value): cached_values.append(value) return stale @cache.memoize(5, is_stale=is_stale) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) # is_stale is only consulted on a cache hit assert cached_values == [] assert big_foo(5, 2) == result assert cached_values == [result] stale = True new_result = big_foo(5, 2) assert new_result != result assert cached_values == [result, result] stale = False assert big_foo(5, 2) == new_result assert cached_values == [result, result, new_result] def test_memoize_is_stale_parameters(app, cache): with app.test_request_context(): call_params = [] def is_stale(value, a, b): call_params.append((value, a, b)) return False @cache.memoize(5, is_stale=is_stale) def memoized_func(a, b): return a + b + random.randrange(0, 100000) result = memoized_func(5, 2) assert call_params == [] assert memoized_func(5, 2) == result # the cached value comes first, followed by the call's own arguments assert call_params == [(result, 5, 2)] def test_memoize_multiple_arg_kwarg_calls(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b, c=None, d=None): if c is None: c = [1, 1] if d is None: d = [1, 1] return sum(a) + sum(b) + sum(c) + sum(d) + random.randrange(0, 100000) result_a = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert big_foo([5, 3, 2], [1], d=[3, 3], c=[3, 3]) == result_a assert big_foo(b=[1], a=[5, 3, 2], c=[3, 3], d=[3, 3]) == result_a assert big_foo([5, 3, 2], [1], [3, 3], [3, 3]) == result_a def test_memoize_multiple_arg_kwarg_delete(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b, c=None, d=None): if c is None: c = [1, 1] if d is None: d = [1, 1] return sum(a) + sum(b) + sum(c) + sum(d) + random.randrange(0, 100000) result_a = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) cache.delete_memoized(big_foo, [5, 3, 2], [1], [3, 3], [3, 3]) result_b = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert result_a != result_b cache.delete_memoized(big_foo, [5, 3, 2], b=[1], c=[3, 3], d=[3, 3]) result_b = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert result_a != result_b cache.delete_memoized(big_foo, [5, 3, 2], [1], c=[3, 3], d=[3, 3]) result_a = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert result_a != result_b cache.delete_memoized(big_foo, [5, 3, 2], b=[1], c=[3, 3], d=[3, 3]) result_a = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert result_a != result_b cache.delete_memoized(big_foo, [5, 3, 2], [1], c=[3, 3], d=[3, 3]) result_b = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert result_a != result_b cache.delete_memoized(big_foo, [5, 3, 2], [1], [3, 3], [3, 3]) result_a = big_foo([5, 3, 2], [1], c=[3, 3], d=[3, 3]) assert result_a != result_b def test_memoize_kwargs_to_args(app, cache): with app.test_request_context(): def big_foo(a, b, c=None, d=None): return sum(a) + sum(b) + random.randrange(0, 100000) expected = (1, 2, "foo", "bar") args, kwargs = cache._memoize_kwargs_to_args(big_foo, 1, 2, "foo", "bar") assert args == expected args, kwargs = cache._memoize_kwargs_to_args(big_foo, 2, "foo", "bar", a=1) assert args == expected args, kwargs = cache._memoize_kwargs_to_args( big_foo, a=1, b=2, c="foo", d="bar" ) assert args == expected args, kwargs = cache._memoize_kwargs_to_args( big_foo, d="bar", b=2, a=1, c="foo" ) assert args == expected args, kwargs = cache._memoize_kwargs_to_args(big_foo, 1, 2, d="bar", c="foo") assert args == expected def test_memoize_when_using_args_unpacking(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(*args): return sum(args) + random.randrange(0, 100000) result_a = big_foo(1, 2) result_b = big_foo(1, 3) assert big_foo(1, 2) == result_a assert big_foo(1, 3) == result_b assert big_foo(1, 2) != result_b assert big_foo(1, 3) != result_a cache.delete_memoized(big_foo) assert big_foo(1, 2) != result_a assert big_foo(1, 3) != result_b def test_memoize_when_using_variable_mix_args_unpacking(app, cache): with app.test_request_context(): @cache.memoize() def big_foo(a, b, *args, **kwargs): return ( sum([a, b]) + sum(args) + sum(kwargs.values()) + random.randrange(0, 100000) ) result_a = big_foo(1, 2, 3, 4, x=2, y=5) result_b = big_foo(4, 7, 7, 2, x=1, y=4) assert big_foo(1, 2, 3, 4, x=2, y=5) == result_a assert big_foo(4, 7, 7, 2, x=1, y=4) == result_b assert big_foo(1, 2, 3, 4, x=2, y=5) != result_b assert big_foo(4, 7, 7, 2, x=1, y=4) != result_a cache.delete_memoized(big_foo) assert big_foo(1, 2, 3, 4, x=2, y=5) != result_a assert big_foo(4, 7, 7, 2, x=1, y=4) != result_b def test_memoize_none(app, cache): with app.test_request_context(): from collections import Counter call_counter = Counter() @cache.memoize(cache_none=True) def memoize_none(param): call_counter[param] += 1 memoize_none(1) # The memoized function should have been called assert call_counter[1] == 1 # Next time we call the function, the value should be coming from the # cache... assert memoize_none(1) is None # …thus, the call counter should remain 1 assert call_counter[1] == 1 cache.clear() memoize_none(1) assert call_counter[1] == 2 def test_memoize_never_accept_none(app, cache): """Asserting that when cache_none is False, we always assume a None value returned from .get() means the key is not found """ with app.test_request_context(): from collections import Counter call_counter = Counter() @cache.memoize() def memoize_none(param): call_counter[param] += 1 memoize_none(1) # The memoized function should have been called assert call_counter[1] == 1 # Next time we call the function, the value should be coming from the # cache… # But the value is None and so we treat it as uncached. assert memoize_none(1) is None # …thus, the call counter should increment to 2 assert call_counter[1] == 2 cache.clear() memoize_none(1) assert call_counter[1] == 3 def test_memoize_with_source_check_enabled(app, cache): with app.test_request_context(): @cache.memoize(source_check=True) def big_foo(a, b): return str(time.time()) first_try = big_foo(5, 2) second_try = big_foo(5, 2) assert second_try == first_try @cache.memoize(source_check=True) def big_foo(a, b): return str(time.time()) third_try = big_foo(5, 2) assert third_try[0] != first_try @cache.memoize(source_check=True) def big_foo(a, b): return str(time.time()) forth_try = big_foo(5, 2) assert forth_try == first_try def test_memoize_with_source_check_disabled(app, cache): with app.test_request_context(): @cache.memoize(source_check=False) def big_foo(a, b): return str(time.time()) first_try = big_foo(5, 2) second_try = big_foo(5, 2) assert second_try == first_try @cache.memoize(source_check=False) def big_foo(a, b): return time.time() third_try = big_foo(5, 2) assert third_try == first_try def test_memoize_source_check_from_config(app): """``CACHE_SOURCE_CHECK`` applies when the decorator doesn't override it.""" app.config["CACHE_SOURCE_CHECK"] = True cache = Cache(app) with app.test_request_context(): @cache.memoize() def big_foo(a, b): return str(time.time()) first_try = big_foo(5, 2) assert big_foo(5, 2) == first_try @cache.memoize() def big_foo(a, b): # noqa: F811 return str(time.time()) assert big_foo(5, 2) != first_try def test_memoize_delete_with_source_check_from_config(app): """``CACHE_SOURCE_CHECK`` must reach the ``make_cache_key`` attribute. ``delete_memoized`` builds the key through ``make_cache_key``, which is created when the decorator is applied. Resolving the config value later than that made the two keys disagree, so nothing was deleted. """ app.config["CACHE_SOURCE_CHECK"] = True cache = Cache(app) with app.test_request_context(): @cache.memoize(50) def big_foo(a, b): return a + b + random.randrange(0, 100000) first_try = big_foo(5, 2) assert big_foo(5, 2) == first_try cache.delete_memoized(big_foo, 5, 2) assert big_foo(5, 2) != first_try def test_cached_source_check_from_config_applies_to_first_call(app): """The source hash must be part of the key from the very first call.""" app.config["CACHE_SOURCE_CHECK"] = True cache = Cache(app) @cache.cached(key_prefix="MyBits") def get_random_bits(): return [random.randrange(0, 2) for i in range(50)] with app.test_request_context(): key_before_call = get_random_bits.make_cache_key() get_random_bits() assert key_before_call != "MyBits" assert get_random_bits.make_cache_key() == key_before_call def test_memoize_ignore_args(app, cache): with app.test_request_context(): @cache.memoize(50, args_to_ignore=["b"]) def big_foo(a, b): return a + b + random.randrange(0, 100000) result = big_foo(5, 2) assert big_foo(5, 3) == result def test_memoize_method_ignore_self_arg(app, cache): with app.test_request_context(): class Foo: @cache.memoize(50, args_to_ignore=["self"]) def big_foo(self, a, b): return a + b + random.randrange(0, 100000) assert Foo().big_foo(5, 2) == Foo().big_foo(5, 2) def test_memoize_function_ignore_kwarg(app, cache): with app.test_request_context(): @cache.memoize(50, args_to_ignore=["b"]) def big_foo(a, b): return a + b + random.randrange(0, 100000) assert big_foo(5, 2) == big_foo(5, b=3) pallets-eco-flask-caching-93fd601/tests/test_py314_annotations.py000066400000000000000000000025561524661224300250350ustar00rootroot00000000000000"""Regression tests for issue #636. On Python 3.14+, ``inspect.signature`` and ``inspect.getfullargspec`` eagerly evaluate annotations via PEP 649's ``__annotate__``. Functions whose annotations reference names defined only under ``if TYPE_CHECKING:`` must still be memoizable / cacheable. """ import sys from typing import TYPE_CHECKING import pytest if TYPE_CHECKING: class TypeCheckingOnly: pass pytestmark = pytest.mark.skipif( sys.version_info < (3, 14), reason="requires Python 3.14 __annotate__ / PEP 649 behavior", ) def test_memoize_with_type_checking_annotation(cache): @cache.memoize(60) def fetch(x: int) -> TypeCheckingOnly: return x * 2 assert fetch(3) == 6 # Cache-hit path also goes through get_function_parameters. assert fetch(3) == 6 def test_cached_with_type_checking_annotation(app, cache): @app.route("/py314-annot") @cache.cached(60) def view(extra: TypeCheckingOnly = None): return "ok" client = app.test_client() assert client.get("/py314-annot").data == b"ok" assert client.get("/py314-annot").data == b"ok" def test_memoize_unless_with_type_checking_annotation(cache): def skip(*args, **kwargs) -> TypeCheckingOnly: return False @cache.memoize(60, unless=skip) def fetch(x: int) -> TypeCheckingOnly: return x + 1 assert fetch(1) == 2 pallets-eco-flask-caching-93fd601/tests/test_serializer.py000066400000000000000000000077731524661224300237170ustar00rootroot00000000000000"""Tests for the ``CACHE_SERIALIZER`` configuration option. Every cachelib backed backend holds a ``serializer`` instance that turns values into bytes before they reach the store. ``CACHE_SERIALIZER`` replaces it for the backend Flask-Caching builds. """ import pytest from cachelib.serializers import JSONSerializer from cachelib.serializers import SimpleSerializer from flask_caching import Cache class CountingSerializer(SimpleSerializer): def __init__(self): self.dumped = 0 self.loaded = 0 def dumps(self, value, protocol=None): self.dumped += 1 return super().dumps(value) def loads(self, bvalue): self.loaded += 1 return super().loads(bvalue) class NeedsArguments(SimpleSerializer): def __init__(self, protocol): self.protocol = protocol def test_default_keeps_the_cachelib_serializer(app): cache = Cache(app) assert isinstance(app.extensions["cache"][cache].serializer, SimpleSerializer) def test_instance_is_used_as_is(app): serializer = JSONSerializer() app.config["CACHE_SERIALIZER"] = serializer cache = Cache(app) assert app.extensions["cache"][cache].serializer is serializer def test_class_is_instantiated(app): app.config["CACHE_SERIALIZER"] = JSONSerializer cache = Cache(app) assert isinstance(app.extensions["cache"][cache].serializer, JSONSerializer) def test_serializer_read_from_extension_config(app): cache = Cache(app, config={"CACHE_SERIALIZER": JSONSerializer}) assert isinstance(app.extensions["cache"][cache].serializer, JSONSerializer) def test_configured_serializer_is_used_for_get_and_set(app): serializer = CountingSerializer() app.config["CACHE_SERIALIZER"] = serializer cache = Cache(app) with app.app_context(): cache.set("hi", "hello") assert cache.get("hi") == "hello" assert serializer.dumped == 1 assert serializer.loaded == 1 def test_values_are_stored_in_the_configured_format(app): app.config["CACHE_SERIALIZER"] = JSONSerializer cache = Cache(app) with app.app_context(): cache.set("hi", {"a": 1}) assert cache.cache._cache["hi"][1] == b'{"a": 1}' assert cache.get("hi") == {"a": 1} def test_cachelib_cache_type_gets_the_serializer(app): app.config["CACHE_TYPE"] = "cachelib.SimpleCache" app.config["CACHE_SERIALIZER"] = JSONSerializer cache = Cache(app) assert isinstance(app.extensions["cache"][cache].serializer, JSONSerializer) def test_backend_without_serializer_warns(app): app.config.update( CACHE_TYPE="NullCache", CACHE_NO_NULL_WARNING=True, CACHE_SERIALIZER=JSONSerializer, ) with pytest.warns(UserWarning, match="CACHE_SERIALIZER"): Cache(app) def test_string_serializer_raises(app): app.config["CACHE_SERIALIZER"] = "json" with pytest.raises(ValueError, match="CACHE_SERIALIZER"): Cache(app) def test_non_cachelib_serializer_raises(app): class DuckTyped: def dumps(self, value): return b"" def loads(self, bvalue): return None app.config["CACHE_SERIALIZER"] = DuckTyped() with pytest.raises(ValueError, match="BaseSerializer"): Cache(app) def test_non_cachelib_serializer_class_raises(app): class DuckTyped: def dumps(self, value): return b"" def loads(self, bvalue): return None app.config["CACHE_SERIALIZER"] = DuckTyped with pytest.raises(ValueError, match="BaseSerializer"): Cache(app) def test_serializer_class_needing_arguments_raises(app): app.config["CACHE_SERIALIZER"] = NeedsArguments with pytest.raises(ValueError, match="Couldn't instantiate serializer"): Cache(app) def test_serializer_is_rejected_before_the_backend_is_built(app): app.config["CACHE_TYPE"] = "flask_caching.backends.RedisCache" app.config["CACHE_SERIALIZER"] = "json" with pytest.raises(ValueError, match="CACHE_SERIALIZER"): Cache(app) assert "cache" not in app.extensions pallets-eco-flask-caching-93fd601/tests/test_signals.py000066400000000000000000000124271524661224300231760ustar00rootroot00000000000000import contextlib import pytest from flask_caching import Cache from flask_caching import cache_memoize_hit from flask_caching import cache_memoize_miss from flask_caching import cache_view_hit from flask_caching import cache_view_miss ALL_SIGNALS = (cache_view_hit, cache_view_miss, cache_memoize_hit, cache_memoize_miss) @contextlib.contextmanager def record_signals(*signals): """Connect a recorder to each signal and collect the payloads it receives. Yields a dict mapping each signal to the list of keyword payloads sent while the context manager is active. """ recorded = {signal: [] for signal in signals} # blinker holds receivers weakly, so keep a reference to each one around receivers = [] with contextlib.ExitStack() as stack: for signal in signals: def receiver(sender, _payloads=recorded[signal], **kwargs): _payloads.append(kwargs) receivers.append(receiver) stack.enter_context(signal.connected_to(receiver)) yield recorded @pytest.fixture def signals_cache(app): app.config["CACHE_ENABLE_SIGNALS"] = True return Cache(app) def test_signals_disabled_by_default(app, cache): @app.route("/") @cache.cached() def cached_view(): return "hello" @cache.memoize() def add(a, b): return a + b with record_signals(*ALL_SIGNALS) as recorded: tc = app.test_client() tc.get("/") tc.get("/") add(1, 2) add(1, 2) assert all(payloads == [] for payloads in recorded.values()) def test_cached_view_sends_miss_then_hit(app, signals_cache): @app.route("/") @signals_cache.cached() def cached_view(): return "hello" with record_signals(cache_view_miss, cache_view_hit) as recorded: tc = app.test_client() tc.get("/") assert len(recorded[cache_view_miss]) == 1 assert recorded[cache_view_hit] == [] tc.get("/") assert len(recorded[cache_view_miss]) == 1 assert len(recorded[cache_view_hit]) == 1 miss = recorded[cache_view_miss][0] hit = recorded[cache_view_hit][0] assert miss["cache"] is signals_cache assert hit["cache"] is signals_cache assert miss["cache_key"] == hit["cache_key"] assert miss["args"] == () assert miss["kwargs"] == {} def test_cached_view_signals_are_per_cache_key(app, signals_cache): @app.route("/") @signals_cache.cached() def cached_view(name): return name with record_signals(cache_view_miss, cache_view_hit) as recorded: tc = app.test_client() tc.get("/foo") tc.get("/bar") tc.get("/foo") assert len(recorded[cache_view_miss]) == 2 assert len(recorded[cache_view_hit]) == 1 first_miss, second_miss = recorded[cache_view_miss] assert first_miss["cache_key"] != second_miss["cache_key"] assert recorded[cache_view_hit][0]["cache_key"] == first_miss["cache_key"] def test_memoize_sends_miss_then_hit(app, signals_cache): @signals_cache.memoize() def add(a, b): return a + b with record_signals(cache_memoize_miss, cache_memoize_hit) as recorded: assert add(1, 2) == 3 assert len(recorded[cache_memoize_miss]) == 1 assert recorded[cache_memoize_hit] == [] assert add(1, 2) == 3 assert len(recorded[cache_memoize_miss]) == 1 assert len(recorded[cache_memoize_hit]) == 1 miss = recorded[cache_memoize_miss][0] hit = recorded[cache_memoize_hit][0] assert miss["cache"] is signals_cache assert miss["f"] is add.uncached assert hit["f"] is add.uncached assert miss["cache_key"] == hit["cache_key"] assert miss["args"] == (1, 2) assert miss["kwargs"] == {} def test_memoize_sends_kwargs(app, signals_cache): @signals_cache.memoize() def add(a, b=0): return a + b with record_signals(cache_memoize_miss) as recorded: assert add(1, b=2) == 3 miss = recorded[cache_memoize_miss][0] assert miss["args"] == (1,) assert miss["kwargs"] == {"b": 2} def test_memoize_forced_update_sends_miss(app, signals_cache): forced = False @signals_cache.memoize(forced_update=lambda: forced) def add(a, b): return a + b add(1, 2) with record_signals(cache_memoize_miss, cache_memoize_hit) as recorded: add(1, 2) assert len(recorded[cache_memoize_hit]) == 1 forced = True add(1, 2) assert len(recorded[cache_memoize_miss]) == 1 assert len(recorded[cache_memoize_hit]) == 1 def test_memoize_is_stale_sends_miss(app, signals_cache): stale = False @signals_cache.memoize(is_stale=lambda value: stale) def add(a, b): return a + b add(1, 2) with record_signals(cache_memoize_miss, cache_memoize_hit) as recorded: add(1, 2) assert len(recorded[cache_memoize_hit]) == 1 stale = True add(1, 2) assert len(recorded[cache_memoize_miss]) == 1 assert len(recorded[cache_memoize_hit]) == 1 def test_signals_enabled_through_cache_config(app): cache = Cache(app, config={"CACHE_ENABLE_SIGNALS": True}) @cache.memoize() def add(a, b): return a + b with record_signals(cache_memoize_miss) as recorded: add(1, 2) assert len(recorded[cache_memoize_miss]) == 1 pallets-eco-flask-caching-93fd601/tests/test_stream.html000066400000000000000000000000471524661224300233400ustar00rootroot00000000000000{{ somevar }}pallets-eco-flask-caching-93fd601/tests/test_subclassing.py000066400000000000000000000103261524661224300240470ustar00rootroot00000000000000"""Tests that the decorators go through the public ``Cache`` API. Third-party instrumentation (tracing, metrics) works by subclassing ``Cache`` and wrapping its public methods. Those wrappers only see the traffic caused by ``@cached``/``@memoize`` if the decorators call ``self.get()``/``self.set()`` rather than reaching into ``self.cache`` directly. """ import pytest from flask_caching import Cache class RecordingCache(Cache): """A ``Cache`` subclass that records the public methods it was asked for.""" def __init__(self, *args, **kwargs): self.calls = [] super().__init__(*args, **kwargs) def get(self, *args, **kwargs): self.calls.append("get") return super().get(*args, **kwargs) def has(self, *args, **kwargs): self.calls.append("has") return super().has(*args, **kwargs) def set(self, *args, **kwargs): self.calls.append("set") return super().set(*args, **kwargs) def delete(self, *args, **kwargs): self.calls.append("delete") return super().delete(*args, **kwargs) def delete_many(self, *args, **kwargs): self.calls.append("delete_many") return super().delete_many(*args, **kwargs) def get_many(self, *args, **kwargs): self.calls.append("get_many") return super().get_many(*args, **kwargs) def set_many(self, *args, **kwargs): self.calls.append("set_many") return super().set_many(*args, **kwargs) @pytest.fixture def recording_cache(app): return RecordingCache(app) def test_cached_uses_public_methods(app, recording_cache): @app.route("/") @recording_cache.cached() def cached_view(): return "hello" tc = app.test_client() tc.get("/") assert recording_cache.calls == ["get", "set"] recording_cache.calls.clear() tc.get("/") assert recording_cache.calls == ["get"] def test_cached_cache_none_uses_public_has(app, recording_cache): @app.route("/") @recording_cache.cached(cache_none=True) def cached_view(): return "hello" tc = app.test_client() tc.get("/") assert recording_cache.calls == ["get", "has", "set"] def test_memoize_uses_public_methods(app, recording_cache): with app.test_request_context(): @recording_cache.memoize() def big_foo(a, b): return a + b big_foo(5, 2) assert recording_cache.calls == [ "get_many", "set_many", "get", "set", "get_many", # memoize version gets read "set_many", # memoize version gets updated ] recording_cache.calls.clear() big_foo(5, 2) assert recording_cache.calls == ["get_many", "get"] def test_delete_memoized_uses_public_delete(app, recording_cache): with app.test_request_context(): @recording_cache.memoize() def big_foo(a, b): return a + b big_foo(5, 2) recording_cache.calls.clear() recording_cache.delete_memoized(big_foo, 5, 2) assert "delete" in recording_cache.calls def test_delete_memoized_without_args_uses_public_set_many(app, recording_cache): with app.test_request_context(): @recording_cache.memoize() def big_foo(a, b): return a + b big_foo(5, 2) recording_cache.calls.clear() recording_cache.delete_memoized(big_foo) assert "set_many" in recording_cache.calls def test_delete_memoized_verhash_uses_public_delete_many(app, recording_cache): with app.test_request_context(): @recording_cache.memoize() def big_foo(a, b): return a + b big_foo(5, 2) recording_cache.calls.clear() recording_cache.delete_memoized_verhash(big_foo) assert "delete_many" in recording_cache.calls def test_subclass_overrides_still_cache_correctly(app, recording_cache): """The recording subclass must not change the caching behaviour itself.""" with app.test_request_context(): runs = [] @recording_cache.memoize() def big_foo(a, b): runs.append((a, b)) return a + b assert big_foo(5, 2) == 7 assert big_foo(5, 2) == 7 assert runs == [(5, 2)] pallets-eco-flask-caching-93fd601/tests/test_template.html000066400000000000000000000003621524661224300236600ustar00rootroot00000000000000{% cache 60, "fragment1" %}{{somevar}}{% endcache %} {% cache 60, "fragment1", "key1" %}{{somevar}}{% endcache %} {% cache 60, "fragment1", "key1", somevar %}{{somevar}}{% endcache %} {% cache timeout, "fragment2" %}{{somevar}}{% endcache %} pallets-eco-flask-caching-93fd601/tests/test_templates.py000066400000000000000000000036241524661224300235330ustar00rootroot00000000000000import random import string from flask import render_template from flask import render_template_string from flask_caching import make_template_fragment_key def test_jinjaext_cache(app, cache): somevar = "".join([random.choice(string.ascii_letters) for x in range(6)]) testkeys = [ make_template_fragment_key("fragment1"), make_template_fragment_key("fragment1", vary_on=["key1"]), make_template_fragment_key("fragment1", vary_on=["key1", somevar]), ] delkey = make_template_fragment_key("fragment2") with app.test_request_context(): #: Test if elements are cached render_template("test_template.html", somevar=somevar, timeout=60) for k in testkeys: assert cache.get(k) == somevar assert cache.get(delkey) == somevar #: Test timeout=del to delete key render_template("test_template.html", somevar=somevar, timeout="del") for k in testkeys: assert cache.get(k) == somevar assert cache.get(delkey) is None #: Test rendering templates from strings output = render_template_string( """{% cache 60, "fragment3" %}{{somevar}}{% endcache %}""", somevar=somevar, ) assert cache.get(make_template_fragment_key("fragment3")) == somevar assert output == somevar #: Test backwards compatibility output = render_template_string( """{% cache 30 %}{{somevar}}{% endcache %}""", somevar=somevar ) assert cache.get(make_template_fragment_key("None1")) == somevar assert output == somevar output = render_template_string( """{% cache 30, "fragment4", "fragment5"%}{{somevar}}{% endcache %}""", somevar=somevar, ) k = make_template_fragment_key("fragment4", vary_on=["fragment5"]) assert cache.get(k) == somevar assert output == somevar pallets-eco-flask-caching-93fd601/tests/test_view.py000066400000000000000000000621561524661224300225140ustar00rootroot00000000000000import hashlib import itertools import time from flask import abort from flask import make_response from flask import request from flask.views import View from flask_caching import CachedResponse def test_cached_view(app, cache, clock): counter = itertools.count() @app.route("/") @cache.cached(2) def cached_view(): return str(next(counter)) tc = app.test_client() rv = tc.get("/") first = rv.data.decode("utf-8") clock.advance(1) rv = tc.get("/") assert first == rv.data.decode("utf-8") clock.advance(1) rv = tc.get("/") assert first != rv.data.decode("utf-8") def test_cached_view_class(app, cache, clock): counter = itertools.count() class CachedView(View): @cache.cached(2) def dispatch_request(self): return str(next(counter)) app.add_url_rule("/", view_func=CachedView.as_view("name")) tc = app.test_client() rv = tc.get("/") first = rv.data.decode("utf-8") clock.advance(1) rv = tc.get("/") assert first == rv.data.decode("utf-8") clock.advance(1) rv = tc.get("/") assert first != rv.data.decode("utf-8") def test_async_cached_view(app, cache): import asyncio @app.route("/test-async") @cache.cached(2) async def cached_async_view(): await asyncio.sleep(0) return str(time.time()) tc = app.test_client() rv = tc.get("/test-async") the_time = rv.data.decode("utf-8") rv = tc.get("/test-async") assert the_time == rv.data.decode("utf-8") def test_cached_view_unless(app, cache): counter = itertools.count() @app.route("/a") @cache.cached(5, unless=lambda: True) def non_cached_view(): return str(next(counter)) @app.route("/b") @cache.cached(5, unless=lambda: False) def cached_view(): return str(next(counter)) tc = app.test_client() rv = tc.get("/a") first = rv.data.decode("utf-8") rv = tc.get("/a") assert first != rv.data.decode("utf-8") rv = tc.get("/b") first = rv.data.decode("utf-8") rv = tc.get("/b") assert first == rv.data.decode("utf-8") def test_cached_view_response_filter(app, cache): counter = itertools.count() @app.route("/a") @cache.cached(5, response_filter=lambda x: x[1] < 400) def cached_view(): return (str(next(counter)), app.return_code) tc = app.test_client() # 500 response does not cache app.return_code = 500 rv = tc.get("/a") first = rv.data.decode("utf-8") rv = tc.get("/a") assert first != rv.data.decode("utf-8") # 200 response caches app.return_code = 200 rv = tc.get("/a") first = rv.data.decode("utf-8") rv = tc.get("/a") assert first == rv.data.decode("utf-8") def test_cached_view_forced_update(app, cache): forced_update = False counter = itertools.count() @app.route("/a") @cache.cached(5, forced_update=lambda: forced_update) def view(): return str(next(counter)) tc = app.test_client() rv = tc.get("/a") first = rv.data.decode("utf-8") rv = tc.get("/a") assert first == rv.data.decode("utf-8") forced_update = True rv = tc.get("/a") second = rv.data.decode("utf-8") assert second != first forced_update = False rv = tc.get("/a") assert second == rv.data.decode("utf-8") def test_cached_view_is_stale(app, cache): stale = False counter = itertools.count() @app.route("/a") @cache.cached(5, is_stale=lambda response: stale) def view(): return str(next(counter)) tc = app.test_client() rv = tc.get("/a") first = rv.data.decode("utf-8") rv = tc.get("/a") assert first == rv.data.decode("utf-8") stale = True rv = tc.get("/a") second = rv.data.decode("utf-8") assert second != first stale = False rv = tc.get("/a") assert second == rv.data.decode("utf-8") def test_generate_cache_key_from_different_view(app, cache): @app.route("/cake/") @cache.cached() def view_cake(flavor): # What's the cache key for apple cake? thanks for making me hungry view_cake.cake_cache_key = view_cake.make_cache_key("apple") return str(time.time()) view_cake.cake_cache_key = "" @app.route("/pie/") @cache.cached() def view_pie(flavor): # What's the cache key for apple cake? view_pie.cake_cache_key = view_cake.make_cache_key("apple") return str(time.time()) view_pie.cake_cache_key = "" tc = app.test_client() tc.get("/cake/chocolate") tc.get("/pie/chocolate") assert view_cake.cake_cache_key == view_pie.cake_cache_key # rename/move to seperate module? def test_cache_key_property(app, cache): @app.route("/") @cache.cached(5) def cached_view(): return str(time.time()) assert hasattr(cached_view, "make_cache_key") assert callable(cached_view.make_cache_key) tc = app.test_client() rv = tc.get("/") the_time = rv.data.decode("utf-8") with app.test_request_context(): cache_data = cache.get(cached_view.make_cache_key()) assert the_time == cache_data def test_set_make_cache_key_property(app, cache): @app.route("/") @cache.cached(5) def cached_view(): return str(time.time()) cached_view.make_cache_key = lambda *args, **kwargs: request.args["foo"] tc = app.test_client() rv = tc.get("/?foo=a") a = rv.data.decode("utf-8") rv = tc.get("/?foo=b") b = rv.data.decode("utf-8") assert a != b tc = app.test_client() rv = tc.get("/?foo=a") a_2 = rv.data.decode("utf-8") assert a == a_2 rv = tc.get("/?foo=b") b_2 = rv.data.decode("utf-8") assert b == b_2 def test_make_cache_key_function_property(app, cache): @app.route("//") @cache.memoize(5) def cached_view(foo, bar): return str(time.time()) assert hasattr(cached_view, "make_cache_key") assert callable(cached_view.make_cache_key) tc = app.test_client() rv = tc.get("/a/b") the_time = rv.data.decode("utf-8") cache_key = cached_view.make_cache_key(cached_view.uncached, foo="a", bar="b") cache_data = cache.get(cache_key) assert the_time == cache_data different_key = cached_view.make_cache_key(cached_view.uncached, foo="b", bar="a") different_data = cache.get(different_key) assert the_time != different_data def test_cache_timeout_property(app, cache, clock): counter = itertools.count() @app.route("/") @cache.memoize(2) def cached_view1(): return str(next(counter)) @app.route("//") @cache.memoize(4) def cached_view2(foo, bar): return str(next(counter)) assert hasattr(cached_view1, "cache_timeout") assert hasattr(cached_view2, "cache_timeout") assert cached_view1.cache_timeout == 2 assert cached_view2.cache_timeout == 4 # test that this is a read-write property cached_view1.cache_timeout = 5 cached_view2.cache_timeout = 7 assert cached_view1.cache_timeout == 5 assert cached_view2.cache_timeout == 7 tc = app.test_client() rv1 = tc.get("/") body1 = rv1.data.decode("utf-8") clock.advance(1) rv2 = tc.get("/a/b") body2 = rv2.data.decode("utf-8") # VIEW1 # it's been 1 second, cache is still active assert body1 == tc.get("/").data.decode("utf-8") clock.advance(5) # it's been >5 seconds, cache is not still active assert body1 != tc.get("/").data.decode("utf-8") # VIEW2 # it's been 6 seconds, cache is still active assert body2 == tc.get("/a/b").data.decode("utf-8") clock.advance(3) # it's been >7 seconds, cache is not still active assert body2 != tc.get("/a/b").data.decode("utf-8") def test_cache_timeout_dynamic_via_cached_reponse(app, cache, clock): counter = itertools.count() @app.route("/") @cache.cached(timeout=1) def cached_view(): # This should override the timeout to be 2 seconds return CachedResponse(response=make_response(str(next(counter))), timeout=2) tc = app.test_client() rv1 = tc.get("/") body = rv1.data.decode("utf-8") clock.advance(1) # it's been 1 second, cache is still active assert body == tc.get("/").data.decode("utf-8") clock.advance(1) # it's been >2 seconds, cache is not still active assert body != tc.get("/").data.decode("utf-8") def test_generate_cache_key_from_query_string(app, cache): """Test the _make_cache_key_query_string() cache key maker. Create three requests to verify that the same query string parameters (key/value) always reference the same cache, regardless of the order of parameters. Also test to make sure that the same cache isn't being used for any/all query string parameters. For example, these two requests should yield the same cache/cache key: * GET /v1/works?mock=true&offset=20&limit=15 * GET /v1/works?limit=15&mock=true&offset=20 Caching functionality is verified by a `@cached` route `/works` which produces a time in its response. The time in the response can verify that two requests with the same query string parameters/values, though differently ordered, produce responses with the same time. """ @app.route("/works") @cache.cached(query_string=True) def view_works(): return str(time.time()) tc = app.test_client() # Make our first query... first_response = tc.get("/works?mock=true&offset=20&limit=15") first_time = first_response.get_data(as_text=True) # Make the second query... second_response = tc.get("/works?limit=15&mock=true&offset=20") second_time = second_response.get_data(as_text=True) # Now make sure the time for the first and second # query are the same! assert second_time == first_time # Last/third query with different parameters/values should # produce a different time. third_response = tc.get("/v1/works?limit=20&mock=true&offset=60") third_time = third_response.get_data(as_text=True) # ... making sure that different query parameter values # don't yield the same cache! assert not third_time == second_time def test_generate_cache_key_from_query_string_repeated_paramaters(app, cache): """Test the _make_cache_key_query_string() cache key maker's support for repeated query paramaters URL params can be repeated with different values. Flask's MultiDict supports them """ @app.route("/works") @cache.cached(query_string=True) def view_works(): flatted_values = sum(request.args.listvalues(), []) return str(sorted(flatted_values)) + str(time.time()) tc = app.test_client() # Make our first query... first_response = tc.get("/works?mock=true&offset=20&limit=15&user[]=123&user[]=124") first_time = first_response.get_data(as_text=True) # Make the second query... second_response = tc.get( "/works?mock=true&offset=20&limit=15&user[]=124&user[]=123" ) second_time = second_response.get_data(as_text=True) # Now make sure the time for the first and second # query are the same! assert second_time == first_time # Last/third query with different parameters/values should # produce a different time. third_response = tc.get("/works?mock=true&offset=20&limit=15&user[]=125&user[]=124") third_time = third_response.get_data(as_text=True) # ... making sure that different query parameter values # don't yield the same cache! assert not third_time == second_time def test_generate_cache_key_from_request_body(app, cache): """Test a user supplied cache key maker. Create three requests to verify that the same request body always reference the same cache Also test to make sure that the same cache isn't being used for any/all query string parameters. Caching functionality is verified by a `@cached` route `/works` which produces a time in its response. The time in the response can verify that two requests with the same request body produce responses with the same time. """ def _make_cache_key_request_body(argument): """Create keys based on request body.""" # now hash the request body so it can be # used as a key for cache. request_body = request.get_data(as_text=False) hashed_body = str(hashlib.md5(request_body).hexdigest()) cache_key = request.path + hashed_body return cache_key @app.route("/works/", methods=["POST"]) @cache.cached(make_cache_key=_make_cache_key_request_body) def view_works(argument): return str(time.time()) + request.get_data().decode() tc = app.test_client() # Make our request... first_response = tc.post("/works/arg", data=dict(mock=True, value=1, test=2)) first_time = first_response.get_data(as_text=True) # Make the request... second_response = tc.post("/works/arg", data=dict(mock=True, value=1, test=2)) second_time = second_response.get_data(as_text=True) # Now make sure the time for the first and second # requests are the same! assert second_time == first_time # Last/third request with different body should # produce a different time. third_response = tc.post("/works/arg", data=dict(mock=True, value=2, test=3)) third_time = third_response.get_data(as_text=True) # ... making sure that different request bodies # don't yield the same cache! assert not third_time == second_time def test_cache_with_query_string_and_source_check_enabled(app, cache): """Test the _make_cache_key_query_string() cache key maker with source_check set to True to include the view's function's source code as part of the cache hash key. """ @cache.cached(query_string=True, source_check=True) def view_works(): return str(time.time()) app.add_url_rule("/works", "works", view_works) tc = app.test_client() # Make our first query... first_response = tc.get("/works?mock=true&offset=20&limit=15") first_time = first_response.get_data(as_text=True) # Make our second query... second_response = tc.get("/works?mock=true&offset=20&limit=15") second_time = second_response.get_data(as_text=True) # The cache should yield the same data first and second time assert first_time == second_time # Change the source of the function attached to the view @cache.cached(query_string=True, source_check=True) def view_works(): return str(time.time()) # ... and we override the function attached to the view app.view_functions["works"] = view_works tc = app.test_client() # Make the second query... third_response = tc.get("/works?mock=true&offset=20&limit=15") third_time = third_response.get_data(as_text=True) # Now make sure the time for the first and third # responses are not the same i.e. cached is not used! assert third_time[0] != first_time # Change the source of the function to what it was originally @cache.cached(query_string=True, source_check=True) def view_works(): return str(time.time()) app.view_functions["works"] = view_works tc = app.test_client() # Last/third query with different parameters/values should # produce a different time. forth_response = tc.get("/works?mock=true&offset=20&limit=15") forth_time = forth_response.get_data(as_text=True) # ... making sure that the first value and the forth value are the same # since the source is the same assert forth_time == first_time def test_cache_with_query_string_and_source_check_disabled(app, cache): """Test the _make_cache_key_query_string() cache key maker with source_check set to False to exclude the view's function's source code as part of the cache hash key and to see if changing the source changes the data. """ @cache.cached(query_string=True, source_check=False) def view_works(): return str(time.time()) app.add_url_rule("/works", "works", view_works) tc = app.test_client() # Make our first query... first_response = tc.get("/works?mock=true&offset=20&limit=15") first_time = first_response.get_data(as_text=True) # Make our second query... second_response = tc.get("/works?mock=true&offset=20&limit=15") second_time = second_response.get_data(as_text=True) # The cache should yield the same data first and second time assert first_time == second_time # Change the source of the function attached to the view @cache.cached(query_string=True, source_check=False) def view_works(): return str(time.time()) # ... and we override the function attached to the view app.view_functions["works"] = view_works tc = app.test_client() # Make the second query... third_response = tc.get("/works?mock=true&offset=20&limit=15") third_time = third_response.get_data(as_text=True) # Now make sure the time for the first and third responses are the same # i.e. cached is used since cache will not check for source changes! assert third_time == first_time def test_hit_cache(app, cache, clock): @app.route("/") @cache.cached(2, response_hit_indication=True) def cached_view(): # This should override the timeout to be 2 seconds return {"data": "data"} @app.route("/indication-false") @cache.cached(2, response_hit_indication=False) def indication_false_cached_view(): # This should override the timeout to be 2 seconds return {"data": "data"} tc = app.test_client() assert tc.get("/").headers.get("hit_cache") is None assert tc.get("/").headers.get("hit_cache") == "True" assert tc.get("/").headers.get("hit_cache") == "True" clock.advance(2) assert tc.get("/").headers.get("hit_cache") is None # indication-false assert tc.get("/indication-false").headers.get("hit_cache") is None assert tc.get("/indication-false").headers.get("hit_cache") is None assert tc.get("/indication-false").headers.get("hit_cache") is None clock.advance(2) assert tc.get("/indication-false").headers.get("hit_cache") is None def test_delete_cached_query_string(app, cache): counter = itertools.count() @app.route("/works") @cache.cached(query_string=True) def view_works(): return str(next(counter)) tc = app.test_client() first = tc.get("/works?mock=true&offset=20&limit=15").get_data(as_text=True) assert tc.get("/works?mock=true&offset=20&limit=15").get_data(as_text=True) == first with app.app_context(): assert cache.delete_cached(view_works, "/works", "limit=15&mock=true&offset=20") second = tc.get("/works?mock=true&offset=20&limit=15").get_data(as_text=True) assert second != first with app.app_context(): assert cache.delete_cached( view_works, "/works", {"offset": 20, "limit": 15, "mock": "true"} ) third = tc.get("/works?mock=true&offset=20&limit=15").get_data(as_text=True) assert third != second with app.app_context(): assert cache.delete_cached( view_works, "/works", [("offset", "20"), ("mock", "true"), ("limit", "15")], ) assert tc.get("/works?mock=true&offset=20&limit=15").get_data(as_text=True) != third def test_delete_cached_query_string_only_deletes_matching_arguments(app, cache): counter = itertools.count() @app.route("/works") @cache.cached(query_string=True) def view_works(): return str(next(counter)) tc = app.test_client() kept = tc.get("/works?limit=15").get_data(as_text=True) deleted = tc.get("/works?limit=20").get_data(as_text=True) with app.app_context(): assert cache.delete_cached(view_works, "/works", "limit=20") assert tc.get("/works?limit=15").get_data(as_text=True) == kept assert tc.get("/works?limit=20").get_data(as_text=True) != deleted def test_delete_cached_query_string_repeated_parameters(app, cache): counter = itertools.count() @app.route("/works") @cache.cached(query_string=True) def view_works(): return str(next(counter)) tc = app.test_client() first = tc.get("/works?user[]=123&user[]=124").get_data(as_text=True) with app.app_context(): assert cache.delete_cached(view_works, "/works", "user[]=124&user[]=123") assert tc.get("/works?user[]=123&user[]=124").get_data(as_text=True) != first def test_delete_cached_view(app, cache): counter = itertools.count() @app.route("/user/") @cache.cached() def view_user(name): return f"{name}{next(counter)}" tc = app.test_client() first = tc.get("/user/bob").get_data(as_text=True) assert tc.get("/user/bob").get_data(as_text=True) == first with app.app_context(): assert cache.delete_cached(view_user, "/user/bob") assert tc.get("/user/bob").get_data(as_text=True) != first def test_delete_cached_view_builds_the_path_with_url_for(app, cache): app.config["SERVER_NAME"] = "localhost" counter = itertools.count() @app.route("/user/") @cache.cached() def view_user(name): return f"{name}{next(counter)}" tc = app.test_client() first = tc.get("/user/bob").get_data(as_text=True) with app.app_context(): assert cache.delete_cached(view_user, name="bob") assert tc.get("/user/bob").get_data(as_text=True) != first def test_make_cache_key_outside_request_context(app, cache): app.config["SERVER_NAME"] = "localhost" @app.route("/works") @cache.cached(query_string=True) def view_works(): return "works" @app.route("/user/") @cache.cached() def view_user(name): return name with app.test_request_context("/works?mock=true&limit=15"): in_request = view_works.make_cache_key() with app.test_request_context("/user/bob"): in_request_user = view_user.make_cache_key(name="bob") with app.app_context(): assert ( view_works.make_cache_key(path="/works", query_args="limit=15&mock=true") == in_request ) assert view_user.make_cache_key(name="bob") == in_request_user def test_cached_view_http_exception(app, cache): calls = [] @app.route("/missing") @cache.cached(2) def cached_view(): calls.append(1) abort(404, "no such thing") tc = app.test_client() first = tc.get("/missing") second = tc.get("/missing") assert first.status_code == second.status_code == 404 assert len(calls) == 1 def test_cached_view_http_exception_runs_error_handler(app, cache): calls = [] @app.errorhandler(404) def handle_404(error): return f"handled {error.description}", 404 @app.route("/missing") @cache.cached(2) def cached_view(): calls.append(1) abort(404, "no such thing") tc = app.test_client() tc.get("/missing") cached = tc.get("/missing") assert cached.get_data(as_text=True) == "handled no such thing" assert len(calls) == 1 def test_cached_view_http_exception_expires(app, cache, clock): calls = [] @app.route("/missing") @cache.cached(2) def cached_view(): calls.append(1) abort(404) tc = app.test_client() tc.get("/missing") clock.advance(1) tc.get("/missing") assert len(calls) == 1 clock.advance(2) assert tc.get("/missing").status_code == 404 assert len(calls) == 2 def test_cached_view_http_exception_response_filter_gets_response(app, cache): calls = [] seen = [] def only_success(response): seen.append(response) return response.status_code == 200 @app.route("/down") @cache.cached(2, response_filter=only_success) def cached_view(): calls.append(1) abort(503) tc = app.test_client() assert tc.get("/down").status_code == 503 assert tc.get("/down").status_code == 503 assert [response.status_code for response in seen] == [503, 503] assert len(calls) == 2 def test_view_argument_named_path_does_not_change_the_key(app, cache): counter = itertools.count() @app.route("/database//summary") @cache.cached() def view_summary(path=""): return f"summary{next(counter)}" @app.route("/database//detail") @cache.cached() def view_detail(path=""): return f"detail{next(counter)}" tc = app.test_client() summary = tc.get("/database/db/summary").get_data(as_text=True) detail = tc.get("/database/db/detail").get_data(as_text=True) assert summary.startswith("summary") assert detail.startswith("detail") assert tc.get("/database/db/summary").get_data(as_text=True) == summary assert tc.get("/database/db/detail").get_data(as_text=True) == detail def test_view_argument_named_query_args_does_not_change_the_key(app, cache): counter = itertools.count() @app.route("/works/") @cache.cached(query_string=True) def view_works(query_args): return f"{query_args}{next(counter)}" tc = app.test_client() first = tc.get("/works/a?limit=15").get_data(as_text=True) second = tc.get("/works/a?limit=20").get_data(as_text=True) assert first != second assert tc.get("/works/a?limit=15").get_data(as_text=True) == first assert tc.get("/works/a?limit=20").get_data(as_text=True) == second pallets-eco-flask-caching-93fd601/tests/typing/000077500000000000000000000000001524661224300214315ustar00rootroot00000000000000pallets-eco-flask-caching-93fd601/tests/typing/memoize_typing.py000066400000000000000000000044121524661224300250430ustar00rootroot00000000000000"""Static typing regression tests, checked by mypy and pyright. This module is never imported or executed - only type checked. The annotated assignments fail the type checkers if the decorators stop preserving signatures or stop binding ``self`` on methods. """ from collections.abc import Callable from datetime import timedelta from flask_caching import Cache cache = Cache() @cache.cached(timeout=1) def cached_plain(a: int, b: str = "x") -> bytes: return b"" @cache.memoize(timeout=1) def memoized_plain(a: int, b: str = "x") -> bytes: return b"" class Adder: def __init__(self, initial: int) -> None: self.initial = initial @cache.cached(timeout=1) def cached_add(self, b: int) -> int: return self.initial + b @cache.memoize(timeout=1) def memoized_add(self, b: int) -> int: return self.initial + b @classmethod @cache.memoize(timeout=1) def memoized_cls_add(cls, b: int) -> int: return b def call_through_self(self) -> int: return self.cached_add(1) + self.memoized_add(2) adder = Adder(1) plain_result: bytes = memoized_plain(1, "y") plain_uncached: Callable[[int, str], bytes] = memoized_plain.uncached plain_timeout: int | timedelta | None = memoized_plain.cache_timeout plain_key: str = cached_plain.make_cache_key() memoized_plain.delete_memoized() # ``self`` is bound on instance access and kept on class access memoized_bound: int = adder.memoized_add(2) memoized_unbound: int = Adder.memoized_add(adder, 2) memoized_bound_uncached: int = adder.memoized_add.uncached(adder, 2) memoized_unbound_uncached: int = Adder.memoized_add.uncached(adder, 2) adder.memoized_add.delete_memoized() cached_bound: int = adder.cached_add(2) cached_unbound: int = Adder.cached_add(adder, 2) cached_bound_uncached: int = adder.cached_add.uncached(adder, 2) cached_unbound_uncached: int = Adder.cached_add.uncached(adder, 2) # every way a memoized function can be reached is accepted here cache.delete_memoized(memoized_plain) cache.delete_memoized(memoized_plain, 1, "y") cache.delete_memoized(adder.memoized_add) cache.delete_memoized(Adder.memoized_add, adder, 2) cache.delete_memoized(Adder.memoized_cls_add, Adder) cache.delete_memoized_verhash(memoized_plain) cache.delete_memoized_verhash(adder.memoized_add) pallets-eco-flask-caching-93fd601/uv.lock000066400000000000000000014136021524661224300202700ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.15'", "python_full_version >= '3.12' and python_full_version < '3.15'", "python_full_version < '3.12'", ] [[package]] name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] [[package]] name = "anyio" version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] name = "asgiref" version = "3.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, ] [[package]] name = "ast-serialize" version = "0.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e1/a9/11851c3e02a3fea2ddc9932d1fdc7d2edaeecc0d2e11bc5f2a7fde2b0934/ast_serialize-0.8.0.tar.gz", hash = "sha256:6c37c43e4004dfb42d321ddedc569dc17ff4259296f3af577c9ea46a809bc010", size = 845638, upload-time = "2026-08-07T11:29:02.152Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/34/16/6e520b57cd8c75914b38c670ad4593d13c22911e4306cc7165dab8b0789b/ast_serialize-0.8.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3d822605fa7bb326ef868d25fafced7fc660fa46d9b90c02ea86d5e2f5d325f7", size = 863924, upload-time = "2026-08-07T11:27:34.579Z" }, { url = "https://files.pythonhosted.org/packages/03/e1/48802de9b22a2bcad42ec80601a17e3f69172fe4f590e6311bcc2b323aeb/ast_serialize-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:2efa40b068197d5efb62655b43baadb842ed71c4958cccd3e8b86a35726f0119", size = 1177662, upload-time = "2026-08-07T11:27:36.196Z" }, { url = "https://files.pythonhosted.org/packages/38/d4/323438db76bded3a1f3523a3167b8325916b2ddceb2107a330c6ec9fcf4d/ast_serialize-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:db1b957291bca08c7e72f43a12357b2948e20775d970e3fc3dac0aa3160ab725", size = 1167072, upload-time = "2026-08-07T11:27:37.646Z" }, { url = "https://files.pythonhosted.org/packages/77/82/53c5400b54144b56de8ed7f957fd1ccd97e42482009292ab46121d15f8dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdc0d5b18ff8fb364e87923e47c0a91d0d69dbcaeaa274591f7fd26892cc3a3a", size = 1225497, upload-time = "2026-08-07T11:27:39.225Z" }, { url = "https://files.pythonhosted.org/packages/44/5f/36c07327a8b91303fbf1382c7c3e8a2902072dbe1b9546138a5288e75ff0/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9da7330f3e235bf7da89b8d39205c6350fc0c08a85379743f2df9fff87d6d980", size = 1227101, upload-time = "2026-08-07T11:27:40.799Z" }, { url = "https://files.pythonhosted.org/packages/9d/48/5adf5c67addc7ddb328122208c6d375a84cf154984f412b4087330a157bd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3186969ee66a9863b00acc6523ace44c56974eecb348a7ea4b228d9f0b80e19", size = 1424001, upload-time = "2026-08-07T11:27:42.708Z" }, { url = "https://files.pythonhosted.org/packages/38/a1/70074dd3869d2b0e934f91891d8d6b734361cd3b80f85ca7ece2e668ecdd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40a57b73731be45da4fa41430c4d5dc94a24b3a4faba7b9e069978c0402064ea", size = 1245545, upload-time = "2026-08-07T11:27:44.4Z" }, { url = "https://files.pythonhosted.org/packages/e3/be/53b9c0a8a6399950c2e3546bdfab96d2b299d5b114b47eb94fd3c49c4054/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b9da3ef807eda752502446dfecea3b381c4900b7e27a5d5f4f899eb39951", size = 1248961, upload-time = "2026-08-07T11:27:45.781Z" }, { url = "https://files.pythonhosted.org/packages/eb/13/3651d3812548a2bda15e26e5dd51aadb48cf682d0865370255fcf0e367dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:293cc1c5bfa741f8e3fbe8175b9c07beee487c9a6fdbb25a5acad9f1df2d30a9", size = 1243877, upload-time = "2026-08-07T11:27:47.325Z" }, { url = "https://files.pythonhosted.org/packages/21/a0/521f0bf000f675e9312a4aae2c8ba7a992405d072a85c485e08fd59433b9/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0910c3442a75216dde0f102d854ba2aaa71d2482e0ee213630b9bf29584fba3", size = 1293903, upload-time = "2026-08-07T11:27:49.264Z" }, { url = "https://files.pythonhosted.org/packages/b1/7e/402fc902568aa2ee65865a3e151f000db0153da8ce6b1be4c9c349025f8d/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43dd6d596879bb1cb8a12cc9dae7bb10090a39a35883026c24f82488a195619a", size = 1401070, upload-time = "2026-08-07T11:27:50.947Z" }, { url = "https://files.pythonhosted.org/packages/ff/7c/97d4b66c057f1706fc8be6dd532cc77c988794357c8f4ffdb6adabb39562/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8c9d537f59e936392cfd3597789d1390304dd659efc3c486ce7f40fb6b8a9f53", size = 1502602, upload-time = "2026-08-07T11:27:52.364Z" }, { url = "https://files.pythonhosted.org/packages/89/6f/72cc3b71562001bba46e898ccfbf1844f7939b3e28912736206102f2e5a8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f0190a33d7f97c65e9069f7a7f40499eea6b5cbe260c558378109caf20ce934b", size = 1495848, upload-time = "2026-08-07T11:27:53.803Z" }, { url = "https://files.pythonhosted.org/packages/a0/53/d6f629d1e49308b2f363dae028baa213ec222c9106fa1f7f0d1f7b41499a/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:77308ae6c5cf5264cc0f01a7c556ec77a9e68eb1f61b093534d698139fdc3b14", size = 1556556, upload-time = "2026-08-07T11:27:55.342Z" }, { url = "https://files.pythonhosted.org/packages/ee/22/340f35dd8dfc6d412d53dc20699ca014b8d228db923e8ed4759c512b162c/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8d53a23f27e1ed3a36b2d26fd2a1a6228c8e85a1ed62ff7cdb44bd610769f20a", size = 1417822, upload-time = "2026-08-07T11:27:56.712Z" }, { url = "https://files.pythonhosted.org/packages/11/29/6dde5c13fbebc051d3a6df4ec0a6fd1d5359333cc1193f7f609f3410b4d8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa5e7cb08f96fed9121f77b224151e41caf88feab9d652bb46c78202b6fbeda", size = 1445153, upload-time = "2026-08-07T11:27:58.275Z" }, { url = "https://files.pythonhosted.org/packages/62/c5/f473a8ed030f7a0ca24b9849cca184677a50c053867a7b808c2e1289bbd3/ast_serialize-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:fa70ed4dea0bb18b30a1789c77baa701d0ef30c474f2ccabdea61e25623a8827", size = 1063711, upload-time = "2026-08-07T11:27:59.793Z" }, { url = "https://files.pythonhosted.org/packages/23/63/39e171fcd38ca057c2e1979d5ee81ac7a3502784abe3d83df7454f7a0978/ast_serialize-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d8b3c8eee4c1baef9d4e84d2a59a805501617127be42615cb48970b15b0892b6", size = 1103740, upload-time = "2026-08-07T11:28:01.405Z" }, { url = "https://files.pythonhosted.org/packages/21/1c/d00762b399e7726d68d0a088cc946e3a4c60f1c6176f557608f672f627f3/ast_serialize-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ac4f0a83c55a9b782f79ad55a5247b7db123c1db405959791c2ef886e9710c9f", size = 1076021, upload-time = "2026-08-07T11:28:02.947Z" }, { url = "https://files.pythonhosted.org/packages/4c/11/911210c3c78923273a9211a2b6cfc4c8aa723b30dab3e1c8d19afb983b40/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:86b8a1e6d90467345356098b040150e82fbc26d24a7a202224b13dc1f6264ca0", size = 1177715, upload-time = "2026-08-07T11:28:04.654Z" }, { url = "https://files.pythonhosted.org/packages/77/89/6282881c8587606638db153cbe21e1e0c4d1f3970dee1aa0610a1c62a026/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:39e92ff8e8cb45947fe9007174b2950e1fb098e6abd00266a13cd3bcf6675068", size = 1169347, upload-time = "2026-08-07T11:28:06.1Z" }, { url = "https://files.pythonhosted.org/packages/97/78/a9f846a03a340ff3728c915f23338ca742742f3292700559cdb3ad999b1e/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85d8d18db5b2dfcb3b7e38a4d600ca35504c0ed8a6f75cd1c811e4ffe248a15", size = 1225916, upload-time = "2026-08-07T11:28:07.654Z" }, { url = "https://files.pythonhosted.org/packages/c0/15/aba6ef8a988a6eceb6f0359589aac509e29ae2dba67fd9bfd5af0c3f13e7/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9830ff7e764f74d9eefb01170c61a9f0fd2c027dac5fcb72e064decd57d56371", size = 1227135, upload-time = "2026-08-07T11:28:09.504Z" }, { url = "https://files.pythonhosted.org/packages/94/29/3f63d696ea7c5b8abadcecc3505be51bd900daaccc522ed8322fa5b05a93/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479d9722a4cd21b578f5478074c41e6169f04811996ec881655560f703a5bba", size = 1425040, upload-time = "2026-08-07T11:28:11.044Z" }, { url = "https://files.pythonhosted.org/packages/e2/5d/0aac338604ff59df5774d4304307898982252f325ff7cafe31d52fedcb65/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a63bed264e818cd83eec11feed0f50aa162542b91132ef58afebc857182763a5", size = 1246278, upload-time = "2026-08-07T11:28:12.519Z" }, { url = "https://files.pythonhosted.org/packages/23/ca/9f1ef795bb724719532bd86dbec11e5b66857d3fbe9b6772baec0191a6ed/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d187197d234aa45d6cfa2b096be5f666e8cc2e7eb3722d0ab8926293cf5720c", size = 1250029, upload-time = "2026-08-07T11:28:13.896Z" }, { url = "https://files.pythonhosted.org/packages/dc/25/5e061372d2ed953b9ba3b9c4f73de3b8e9234cda3f6c088db4686801d0e1/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:2d39a56282cfcc0d8eeea37267c754be59c98d48505c23b1dae5c6011f3813dd", size = 1243575, upload-time = "2026-08-07T11:28:15.37Z" }, { url = "https://files.pythonhosted.org/packages/a8/c1/ae7da218053120635a4ca802366c69f707203641af95372eeb83f70dfd52/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7cc5f10386994c0f4844f1e6d6a97127e9b478660eb6dec2b257644f0acab64", size = 1294396, upload-time = "2026-08-07T11:28:16.813Z" }, { url = "https://files.pythonhosted.org/packages/2e/89/271d1f49c5269fcddcc789ea3f25be401f6723fc1138aeda539f4d05516d/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6102f2f985c2e542be85cd857678ec9356fefa792b93cadfadd31139f5696f27", size = 1401987, upload-time = "2026-08-07T11:28:18.333Z" }, { url = "https://files.pythonhosted.org/packages/55/be/4e7d77fcf571ac7cb5cf7115a20c36642bd7d29473b45dfaaefeb9618f90/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:3a8660fe66667b76a6e9dccd1d33e66b229fde3b308db991c041609226c005b6", size = 1502904, upload-time = "2026-08-07T11:28:20.039Z" }, { url = "https://files.pythonhosted.org/packages/8b/ae/ed1de2db7e019d4236fbc164ffa5ef9a6022a300a342bbf142d21b7c141e/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:e7266307e5fba39836edb79def8608887af48820508bff3c5f2941e1e04d1534", size = 1496967, upload-time = "2026-08-07T11:28:21.734Z" }, { url = "https://files.pythonhosted.org/packages/92/89/5fea507fae5c5f18b7dc7f95e5c00956574b8c717b8fd2049c504fab0b18/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca7e6fd1ad845d1cc649dc2ecd499db2f8f46af5bf8da7b70dd858774cc038b", size = 1559041, upload-time = "2026-08-07T11:28:23.194Z" }, { url = "https://files.pythonhosted.org/packages/42/71/478d69df21b64e064554a68134c94be304270316ca676a94e63c389a636a/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2880350b13d3eae69a0d70bc1fb6c9bfaca4dbd0e20ba8cd1aa483080b56ff06", size = 1417367, upload-time = "2026-08-07T11:28:24.601Z" }, { url = "https://files.pythonhosted.org/packages/5e/2d/8962dc8d5b3a9dc27b36f9db199afa25264c741505469d9ec10ffbfd2ba7/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:ab0f9a59f7d63d0d441b56b9a818b273705264352d5115cfee12e940e816d958", size = 1446178, upload-time = "2026-08-07T11:28:26.152Z" }, { url = "https://files.pythonhosted.org/packages/4f/22/14d2ad4fd1d1bcd0dc687ca268e0630069f45162496260c0efb70ee0ea72/ast_serialize-0.8.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:0485a25ef519c62e749ee3c1ad8070e591b380d67226349eb5a70b228dc1ac4a", size = 1063811, upload-time = "2026-08-07T11:28:27.864Z" }, { url = "https://files.pythonhosted.org/packages/18/1d/84a327c0202a41aa5fdba3ade33904d6d8f3b9e6806fa83568d835395850/ast_serialize-0.8.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:bd84d60bca7079e741be4ac5dbe237751a59d7f6f9f0126b11880d63822cbe16", size = 1105518, upload-time = "2026-08-07T11:28:29.691Z" }, { url = "https://files.pythonhosted.org/packages/8c/92/74556dec52fde85a2ad84ed159991b916241043788609c15d8b77e14570b/ast_serialize-0.8.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:057769b5921336eb2d9124f2a731b42ed05ffdac559b840dbdf6f3937cf153dc", size = 1076319, upload-time = "2026-08-07T11:28:31.282Z" }, { url = "https://files.pythonhosted.org/packages/d1/5d/c650b1f2cc1e75193358da95a080261422e8cd10b66d7370b1688c9915c5/ast_serialize-0.8.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:a02cbed7d8bfdcdee88edaac12bd50d53d9953aaa2e1852ef078625be5f1c0b5", size = 852914, upload-time = "2026-08-07T11:28:32.929Z" }, { url = "https://files.pythonhosted.org/packages/d9/e3/6142e920fec6ef7bccabd8c24ed8ed99f8bdc6cb8b065e1df7c6a3b2d667/ast_serialize-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e1bd223df0f6c96b396975fa604cb33bce53d9b4a0185490be4c4a289f7c9c87", size = 1184007, upload-time = "2026-08-07T11:28:34.654Z" }, { url = "https://files.pythonhosted.org/packages/a6/e9/6e8be8df02b35d85e2b8809f7f1cfa290bdf5882b55127a539d049482db0/ast_serialize-0.8.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ddd3b61f45c132da66c5476b281891e08c1fd87fbdabe8a6973e1622efc85f06", size = 1177588, upload-time = "2026-08-07T11:28:36.318Z" }, { url = "https://files.pythonhosted.org/packages/8c/80/7e0fd2e2e2aba257820db4a8657c4c356844d36b914b20a4af294bcfb902/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9caa63fad8241257ae401b5ff0a64026c6adb36b8e86cbe8782d9ea505daf6", size = 1234575, upload-time = "2026-08-07T11:28:37.772Z" }, { url = "https://files.pythonhosted.org/packages/b0/6a/3bae0af06f9b1bae3001c44d64215f5b567877e7aae9ffd45db11c3a7647/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3926fa117b5e65019853a2969966d11c7175af377a3425991f3fe73784412405", size = 1236015, upload-time = "2026-08-07T11:28:39.14Z" }, { url = "https://files.pythonhosted.org/packages/6f/c4/ce2d41a1bc22508e82618901f7e10f2a5e2f9556553fea90624daf9875e2/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:485f1113af805e9e170b95ef993ca3fbd4f89c04bab25c58b4fc632d854801ab", size = 1432808, upload-time = "2026-08-07T11:28:40.664Z" }, { url = "https://files.pythonhosted.org/packages/1a/90/f5058f209756dd70e958b7538aaa82d25d24944baf9ec8ae6f27b06fcacc/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccebbed24f1281062d5852353c72c47502955926cfcb8345ffb3a44d87ff3d3", size = 1256251, upload-time = "2026-08-07T11:28:42.223Z" }, { url = "https://files.pythonhosted.org/packages/bf/32/7f77ea87fa0836daab706ed5cb7f903bb25fa26a77439011aee626af11d8/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252f883290d1cdb728eb7fe1d9a7221b88af5a329aae0bc91ddee4dafb820331", size = 1258574, upload-time = "2026-08-07T11:28:43.751Z" }, { url = "https://files.pythonhosted.org/packages/eb/5a/75b82ad2725b5e8e8c742732f9e76c6738a292d0709e1f60d10a973730b4/ast_serialize-0.8.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:96abc072ad29db8d02194afd47d68987322622787daceae82398d7b69f3ba2e6", size = 1254075, upload-time = "2026-08-07T11:28:45.28Z" }, { url = "https://files.pythonhosted.org/packages/4e/54/8c20ed4eea805516a3fd23dd4a721ce28c64f50f0e4b359969f60a8c97a6/ast_serialize-0.8.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9118ad3e369727060b2696fc4078f250ecffca4248ba87f537f55cea9f9dce06", size = 1301018, upload-time = "2026-08-07T11:28:46.851Z" }, { url = "https://files.pythonhosted.org/packages/cb/5b/9f14430f12fe830b656fb38f8e2e05ee13b02a88967660bef46af0ab22a8/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f359df4bd921918af8bebd142a376c77511d7151cc8ba852760b587b5a4a54f3", size = 1409951, upload-time = "2026-08-07T11:28:48.312Z" }, { url = "https://files.pythonhosted.org/packages/2d/3d/084882eca93c842bd4262591a071ec7f825340644035e51501208cc5a8d4/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e94f9121d13fa36cbf21314783c77d05ae3a0868decd18cf5233fdcc6de49ac8", size = 1509544, upload-time = "2026-08-07T11:28:49.847Z" }, { url = "https://files.pythonhosted.org/packages/ce/73/ea84852096c2036c61cc0b2f97b90242207419f534dc671060ee1c8e05cb/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:54f95b486018d262bcb387a9afd96f0da74508b442762b80c769454a6fbb3ee3", size = 1505671, upload-time = "2026-08-07T11:28:51.239Z" }, { url = "https://files.pythonhosted.org/packages/cb/88/287b9a5300c1f2f651d259f670931b63110adc265b7613c885b44c5bc53d/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c38b915511e32bc718c49dbce98ff9af36bac0ad6a604f58000cd5e3aecdba7", size = 1563685, upload-time = "2026-08-07T11:28:53.112Z" }, { url = "https://files.pythonhosted.org/packages/ee/f3/1bc3a79afcf0c2a8d2c37182d0d659d1545a9d7f7f6dc9cf3e63d6c17135/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:9a2ef9cf12f2de4f1028c42c1dd7d775255e0fb3e5bb48896c97e35ef52366fe", size = 1427977, upload-time = "2026-08-07T11:28:54.418Z" }, { url = "https://files.pythonhosted.org/packages/5c/cd/440c798957e14e31776bfeb024d8fafe0bb1d5b89c51c2f067e69938f7b0/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f18048fe9f6dd266bd577cdec48bdcecb74faaa01fe941324435483b013ed2a", size = 1454335, upload-time = "2026-08-07T11:28:55.968Z" }, { url = "https://files.pythonhosted.org/packages/4f/4a/587eb36dcc240a54c8660f599464516b469ecad96f0dbdb6bccbedb50745/ast_serialize-0.8.0-cp39-abi3-win32.whl", hash = "sha256:31883542dd6c94d178f5db3d32fbd69c5eb88b3a7c018e7ac8cc0c45195ddbed", size = 1068858, upload-time = "2026-08-07T11:28:57.541Z" }, { url = "https://files.pythonhosted.org/packages/5f/a4/3e887bbd92164e183cb6e412c6a3e9198ddd446d7fe405958293ef5ef49c/ast_serialize-0.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:861794565b06337005c1447ef23103a3d5a627d08bdc827870d00d0b28ef5f51", size = 1111839, upload-time = "2026-08-07T11:28:59Z" }, { url = "https://files.pythonhosted.org/packages/25/6c/b400476d3ceba681ab929787edc9554f6d88fcc69435eb681b00fc0457a5/ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0", size = 1083655, upload-time = "2026-08-07T11:29:00.349Z" }, ] [[package]] name = "async-timeout" version = "5.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] name = "blinker" version = "1.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] [[package]] name = "cachelib" version = "0.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c6/f4/b20875916b83f68775093554ce2544b12255396ba69abd93d8903cce0feb/cachelib-0.17.0.tar.gz", hash = "sha256:f3c7dc8d3c1132ab699681ffdf8a52d341d9425ac1401c538cf0b1d87b1677c8", size = 135529, upload-time = "2026-08-24T00:40:51.851Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f5/87/9110494f2816d3f2907ac9a0a0a5387f34bc4fa9755721ad09f0a2c99e9b/cachelib-0.17.0-py3-none-any.whl", hash = "sha256:f83909b6f78741c3a5d76d292d13bf24964ffb13e00ea1d18f92e20599766ce0", size = 28221, upload-time = "2026-08-24T00:40:50.237Z" }, ] [[package]] name = "cachetools" version = "7.1.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/70/d2/47e8bc06fe2a06d3f5bdf20f1126ab66c4e99dc48d940e7ba873f7ac7131/cachetools-7.1.7.tar.gz", hash = "sha256:a3e2a00b14d8f8a6b70c1dae7b4685e7ad3bc965c5b42124a2d6ce895da6cf50", size = 40680, upload-time = "2026-08-01T21:20:40.434Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/d8/767faeda872075724b95dd675466a645f1b92aadcdcf2d1429dcfd76c176/cachetools-7.1.7-py3-none-any.whl", hash = "sha256:ef98ef375ad188819ef2f9b3645e3987f4b8c5b7550e436ad998c2de78296df0", size = 16830, upload-time = "2026-08-01T21:20:38.977Z" }, ] [[package]] name = "certifi" version = "2026.7.22" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] name = "cffi" version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" }, { url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" }, { url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" }, { url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" }, { url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" }, { url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" }, { url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" }, { url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" }, { url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" }, { url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" }, { url = "https://files.pythonhosted.org/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470, upload-time = "2026-08-03T21:19:41.246Z" }, { url = "https://files.pythonhosted.org/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096, upload-time = "2026-08-03T21:19:42.615Z" }, { url = "https://files.pythonhosted.org/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941, upload-time = "2026-08-03T21:19:43.747Z" }, { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, ] [[package]] name = "cfgv" version = "3.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] name = "charset-normalizer" version = "3.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] [[package]] name = "click" version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.15.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/66/edcec7d7a0b524aa8923e22925fde6fe50ce005a113dca13ae1581455c4c/coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490", size = 222367, upload-time = "2026-08-06T13:47:15.578Z" }, { url = "https://files.pythonhosted.org/packages/e6/c6/ab8de429e2e8548faf58ec7e1674a4ce00414b4113942d3fe87109cf0f68/coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e", size = 222874, upload-time = "2026-08-06T13:47:16.961Z" }, { url = "https://files.pythonhosted.org/packages/be/c4/3b7b49587e8a6b9af79b3eb468d443d6042b6d65b47aa26586846a0d6566/coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7", size = 253287, upload-time = "2026-08-06T13:47:18.291Z" }, { url = "https://files.pythonhosted.org/packages/fb/65/ec03b743a2a229c72cc1eff3e57be9d3564e9c6b4d5aba2d70744a3fc0d8/coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6", size = 255199, upload-time = "2026-08-06T13:47:19.765Z" }, { url = "https://files.pythonhosted.org/packages/41/4b/5163729e4b6582d61975cfd3ccab45b4ec53e21cf156d9941cb025188468/coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d", size = 257308, upload-time = "2026-08-06T13:47:21.206Z" }, { url = "https://files.pythonhosted.org/packages/86/08/2167a0f08fb87d702fa423a48578a32865464b7c9e1db3911ad7812ab414/coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce", size = 259268, upload-time = "2026-08-06T13:47:22.503Z" }, { url = "https://files.pythonhosted.org/packages/1e/e5/68eebae3053dbd48508edea559c21b23fbdf3460784f91370c83a86a6acd/coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7", size = 253392, upload-time = "2026-08-06T13:47:23.88Z" }, { url = "https://files.pythonhosted.org/packages/1a/46/fd4ced40a2b691c774e515c9b69500bfa64c7960b67fcee4b2f6fad97fc3/coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b", size = 255001, upload-time = "2026-08-06T13:47:25.469Z" }, { url = "https://files.pythonhosted.org/packages/53/25/ae2e5fa710bb6957a9aadeb9e3598d3b3e4af6587ce857ad42e8639a3f30/coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc", size = 253061, upload-time = "2026-08-06T13:47:26.845Z" }, { url = "https://files.pythonhosted.org/packages/d7/31/67ddc0365db2c6e93ac8580bc4bbc50f65273262f973f63ebcdbc15c0495/coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571", size = 256831, upload-time = "2026-08-06T13:47:28.217Z" }, { url = "https://files.pythonhosted.org/packages/f6/78/82b8fd18f57fb13f12d98fe874995bb2c4f9f17be8aff762c426323fdb96/coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719", size = 252781, upload-time = "2026-08-06T13:47:29.712Z" }, { url = "https://files.pythonhosted.org/packages/0a/eb/6c74ef4dd12b252e573c49bdef9e2ac265bf3dbb79b8d7feb3266e084e9e/coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7", size = 253692, upload-time = "2026-08-06T13:47:31.192Z" }, { url = "https://files.pythonhosted.org/packages/5a/66/eb9aed1c3fd2d36ee00eb173f434b14fa607fc056739c9a89ff4244010ea/coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e", size = 224461, upload-time = "2026-08-06T13:47:32.572Z" }, { url = "https://files.pythonhosted.org/packages/e2/6d/81fa4161dfb3ed9d74e40d58647eff83a56b7612e78352581280fce2f477/coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc", size = 224937, upload-time = "2026-08-06T13:47:34.205Z" }, { url = "https://files.pythonhosted.org/packages/5b/c1/d8dacf683c6cad3cf85ce68fd3774a6774ec402128822fdfaed920f11e6a/coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890", size = 224479, upload-time = "2026-08-06T13:47:36.118Z" }, { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, { url = "https://files.pythonhosted.org/packages/8c/3f/f0642a372f494bd0d7dad3b497083b910194a5f1c88be2c94fef707c3b59/coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2", size = 257145, upload-time = "2026-08-06T13:47:41.931Z" }, { url = "https://files.pythonhosted.org/packages/71/17/8b46d0ed68251016002ec972c8fc0119961a765d0984cafb8bf317c43758/coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931", size = 258257, upload-time = "2026-08-06T13:47:43.527Z" }, { url = "https://files.pythonhosted.org/packages/30/b8/8498a0e72d0adbe15477dd07463d2b3bb2c9f6a4815e8589e50939e2c3ae/coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8", size = 260517, upload-time = "2026-08-06T13:47:45.121Z" }, { url = "https://files.pythonhosted.org/packages/41/e1/7dce19c3bdb1e3dd63e769508216500edad81bd5f69a26d724e32aceaf78/coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9", size = 254785, upload-time = "2026-08-06T13:47:46.541Z" }, { url = "https://files.pythonhosted.org/packages/dd/b1/e1494703c675a2561723cd9b89f45c9168782c31280c611b1f767851e57c/coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839", size = 256176, upload-time = "2026-08-06T13:47:48.155Z" }, { url = "https://files.pythonhosted.org/packages/73/76/a5629d270fb638a43a4b10466f51e2f49d532c1aa4da2913cbbb150bbe0a/coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72", size = 254321, upload-time = "2026-08-06T13:47:49.757Z" }, { url = "https://files.pythonhosted.org/packages/ff/4f/9c44447218435d5766b911534f9d798144a5560f85e9a54ebe5f3f5d19f9/coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52", size = 258390, upload-time = "2026-08-06T13:47:51.248Z" }, { url = "https://files.pythonhosted.org/packages/de/36/c1e127616fb3fa18a9ff71e76c417f2fd7424332a4870015ac224ef4c039/coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c", size = 253894, upload-time = "2026-08-06T13:47:52.816Z" }, { url = "https://files.pythonhosted.org/packages/e9/b9/fdb92c8ae7a8bb9b850cc253b7b3b9c8526f68130002048b5671cd510d09/coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4", size = 255763, upload-time = "2026-08-06T13:47:54.296Z" }, { url = "https://files.pythonhosted.org/packages/6f/c0/a7d51b2587c7bdb76e71b0896d2565bf7d60436b5122fc83e511adb1f7cd/coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b", size = 224597, upload-time = "2026-08-06T13:47:56.074Z" }, { url = "https://files.pythonhosted.org/packages/49/b9/5c5f80cc55f5acaaca6dee677626bfcec8c87204a7809b438b08e84f4571/coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd", size = 225135, upload-time = "2026-08-06T13:47:57.52Z" }, { url = "https://files.pythonhosted.org/packages/47/e4/2a4561f89ff6bf7c925c287d0f2cce8bdf139c3a33735c87e3203401cf94/coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f", size = 224515, upload-time = "2026-08-06T13:47:58.977Z" }, { url = "https://files.pythonhosted.org/packages/f1/84/651a9310859673aaa3b3203f1aa1641ca60fcf2494683e1c9474c7172780/coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921", size = 222565, upload-time = "2026-08-06T13:48:00.796Z" }, { url = "https://files.pythonhosted.org/packages/82/f9/4dcf700137e8af550670f4d74d1b63828ce93e1e2b05e5f10710eb2ea987/coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e", size = 222936, upload-time = "2026-08-06T13:48:02.391Z" }, { url = "https://files.pythonhosted.org/packages/07/4a/612ff1e780b3fbfd637486f542f84adc5503873d8b5d279dec1ffeef9414/coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36", size = 253926, upload-time = "2026-08-06T13:48:04.382Z" }, { url = "https://files.pythonhosted.org/packages/b0/04/d1cff1c2ead4708a6a79c01d3736b6a25bd38a36678398f72a8dd33dfad9/coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4", size = 256523, upload-time = "2026-08-06T13:48:05.996Z" }, { url = "https://files.pythonhosted.org/packages/b9/80/d34e13fb4b293cbdb9665838cf5522077b8ad14ef947550631a4bced36a5/coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c", size = 257759, upload-time = "2026-08-06T13:48:08.036Z" }, { url = "https://files.pythonhosted.org/packages/0f/e7/2c5fe7636fdb0732fe0f09f308a5b066864078b7fc61f6678e8478554f2e/coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7", size = 259890, upload-time = "2026-08-06T13:48:09.834Z" }, { url = "https://files.pythonhosted.org/packages/92/28/9689f0858dfff59c2ea688938ab9fa2925631235df67126a42b6c5c70ae1/coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25", size = 254121, upload-time = "2026-08-06T13:48:11.459Z" }, { url = "https://files.pythonhosted.org/packages/f9/e2/785077c230c157243eb5aa9a26c3be260ecd02001bead54a3cada3df8e03/coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b", size = 255891, upload-time = "2026-08-06T13:48:13.209Z" }, { url = "https://files.pythonhosted.org/packages/d4/90/e20371b17b40f912f21305c2db2f30efa3de306f7320fc916804872c85a4/coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78", size = 253859, upload-time = "2026-08-06T13:48:14.736Z" }, { url = "https://files.pythonhosted.org/packages/05/49/25371987ee459a5f67c0427fb75c74f9358e65f2c71fe75bf41c1b6c5fcb/coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f", size = 258011, upload-time = "2026-08-06T13:48:16.464Z" }, { url = "https://files.pythonhosted.org/packages/30/6e/32e67467f6154bf4f1c4f63b05acc5097cba4237d45bbeeea446b52e8ac1/coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d", size = 253676, upload-time = "2026-08-06T13:48:18.493Z" }, { url = "https://files.pythonhosted.org/packages/03/c1/8b24192e89286399765155251f99ee9f070a9d637109018ac23d99b99f6f/coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff", size = 255453, upload-time = "2026-08-06T13:48:20.057Z" }, { url = "https://files.pythonhosted.org/packages/16/6f/8b41ebdf67c87854e17c035336a90f1cfbad0c14c2a584301be6ff148718/coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c", size = 224605, upload-time = "2026-08-06T13:48:21.655Z" }, { url = "https://files.pythonhosted.org/packages/e0/e2/2946c7f0b42b152ecb21ff1bdad72e3d301e790c0c487e4a86e8c9f69347/coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4", size = 225148, upload-time = "2026-08-06T13:48:23.376Z" }, { url = "https://files.pythonhosted.org/packages/9e/83/3f4a69957f48ae7a0aba76c34743f88963d607b19e03f3f8e66f91cae0f9/coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf", size = 224536, upload-time = "2026-08-06T13:48:25.117Z" }, { url = "https://files.pythonhosted.org/packages/ea/ac/748cf29eeb2d6be34a3176ce26a4f49e38085ee08e8935f05f6f26ed7e0f/coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f", size = 222608, upload-time = "2026-08-06T13:48:26.806Z" }, { url = "https://files.pythonhosted.org/packages/0b/02/1abbf5c984677b0aa439cdacaccbf38d248939d8ef8fe1cc7a50d73edb77/coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c", size = 222940, upload-time = "2026-08-06T13:48:28.432Z" }, { url = "https://files.pythonhosted.org/packages/eb/e1/ff8f9f53d9fcf586125b55d0b1f04ec1c14955fee41e83d5814bee141bb5/coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082", size = 253985, upload-time = "2026-08-06T13:48:29.995Z" }, { url = "https://files.pythonhosted.org/packages/a1/26/595759762e514e81be1d7d01ed03444303bcd152226a6529998d253f9201/coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac", size = 256492, upload-time = "2026-08-06T13:48:31.634Z" }, { url = "https://files.pythonhosted.org/packages/24/68/b79aabac54d482be23b5fcdd4f4662bff24a78edc4ee29201726929936d5/coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734", size = 257837, upload-time = "2026-08-06T13:48:33.186Z" }, { url = "https://files.pythonhosted.org/packages/09/0f/bf7f297885a5bf6fd71e5782404e0ff059ca09e8711ceb3a08544abde45a/coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d", size = 260152, upload-time = "2026-08-06T13:48:34.75Z" }, { url = "https://files.pythonhosted.org/packages/fd/f1/296744e854ff8368542343457414380465e9ceefb9192342feb9d3bc461d/coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf", size = 253978, upload-time = "2026-08-06T13:48:36.434Z" }, { url = "https://files.pythonhosted.org/packages/55/b0/bbdb2e9057493e66220a2e149ca2d301ba0e3a58a83bd6b90de9826d16f3/coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b", size = 255846, upload-time = "2026-08-06T13:48:38.317Z" }, { url = "https://files.pythonhosted.org/packages/96/e4/38015b2b6d21258713bd17e76b59d033b191efb5703589cffd037dfbca20/coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25", size = 253808, upload-time = "2026-08-06T13:48:39.993Z" }, { url = "https://files.pythonhosted.org/packages/0b/64/0d515c1e60ee6fbfd1a0e79c07cd87d388a233b7adc37758735677203808/coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303", size = 258081, upload-time = "2026-08-06T13:48:41.971Z" }, { url = "https://files.pythonhosted.org/packages/91/71/04d9e7a3642146c6351338aef4ef85ab11dbbb54744c13245caba1aad1c0/coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f", size = 253624, upload-time = "2026-08-06T13:48:43.731Z" }, { url = "https://files.pythonhosted.org/packages/b4/a7/6c28b74c81ebff66987b0e2522ba5cffa3e90b0c33cb6a2eb264d4ee8cf1/coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5", size = 255280, upload-time = "2026-08-06T13:48:45.58Z" }, { url = "https://files.pythonhosted.org/packages/52/af/bc19996a7014b98d7bbb0f0939453c67074af65784a3aa16a789a07381fa/coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7", size = 224768, upload-time = "2026-08-06T13:48:47.525Z" }, { url = "https://files.pythonhosted.org/packages/ee/90/219484e476d6e101ba0a444852579e05f5b75c37c611a42ed1190f73ef62/coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425", size = 225259, upload-time = "2026-08-06T13:48:49.513Z" }, { url = "https://files.pythonhosted.org/packages/b7/66/fa77daf4e383e5f776dac62c2409b6af81910ae6fe326bd5170dba74cc63/coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8", size = 224684, upload-time = "2026-08-06T13:48:51.235Z" }, { url = "https://files.pythonhosted.org/packages/58/5b/f03bf0ce362bbf3f785fa5219620d00778d4ac6fc9e407734828e9c672f6/coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8", size = 223338, upload-time = "2026-08-06T13:48:52.896Z" }, { url = "https://files.pythonhosted.org/packages/0f/76/e77d0ae22501831cc9f92193e8a957a5caa1dd177f90a6d1d9b106242d92/coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a", size = 223609, upload-time = "2026-08-06T13:48:54.688Z" }, { url = "https://files.pythonhosted.org/packages/82/1a/b1f089da8d38ac612fa2dd6dc7f4a1a7657d12f3e261d2996edd3a838d0b/coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b", size = 264970, upload-time = "2026-08-06T13:48:56.403Z" }, { url = "https://files.pythonhosted.org/packages/bf/31/e66d98d6e9c7fcc88470f1e234eaf6b1950dc0dfbf797f7282c1c861da24/coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5", size = 267088, upload-time = "2026-08-06T13:48:58.41Z" }, { url = "https://files.pythonhosted.org/packages/59/a1/ae94eb2c541add426378408379f233591e069040b1e2cdb33df9498a0682/coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba", size = 269508, upload-time = "2026-08-06T13:49:00.42Z" }, { url = "https://files.pythonhosted.org/packages/9c/c7/88a10694a1c6a213569766aba9f25847b28155d4ac731b13226db216356d/coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982", size = 270629, upload-time = "2026-08-06T13:49:02.234Z" }, { url = "https://files.pythonhosted.org/packages/b3/34/d8b8232e5e55169933b59aabcef2fedfa4b9d8897361bb80fcbda146505f/coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c", size = 264043, upload-time = "2026-08-06T13:49:04.102Z" }, { url = "https://files.pythonhosted.org/packages/7e/35/58b009dbf8c471c7224716478b9fed4a7e1af15320e1ed41660978504663/coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57", size = 266963, upload-time = "2026-08-06T13:49:05.821Z" }, { url = "https://files.pythonhosted.org/packages/62/aa/57fbda1b42c892968273c56b6ee9dc0f1310850859230a507bc7873b1f65/coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26", size = 264569, upload-time = "2026-08-06T13:49:07.706Z" }, { url = "https://files.pythonhosted.org/packages/98/8a/360e6e7f24d477b7e889703af0afa878d15b6d4d8d2a822b2835c169a879/coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429", size = 268299, upload-time = "2026-08-06T13:49:09.587Z" }, { url = "https://files.pythonhosted.org/packages/4e/89/6f701261aee21b6b5fa8f7872229406dc917e125069448292223bf213606/coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017", size = 263413, upload-time = "2026-08-06T13:49:11.604Z" }, { url = "https://files.pythonhosted.org/packages/3f/0f/6f04036edc260ed425af83e834f627fad48941ce97b50bfe6edd8b6fa623/coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839", size = 265725, upload-time = "2026-08-06T13:49:13.38Z" }, { url = "https://files.pythonhosted.org/packages/c4/ce/d19b5d4d5c49a7bfb925fd74310fee7d28bc99520ac3367ccbc54e662518/coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85", size = 225079, upload-time = "2026-08-06T13:49:15.265Z" }, { url = "https://files.pythonhosted.org/packages/26/bb/7aa1b3b173faee0679037ca950bbbe1247273656697994d8d13f80f8d4b4/coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e", size = 225911, upload-time = "2026-08-06T13:49:17.279Z" }, { url = "https://files.pythonhosted.org/packages/81/1c/4ea9e47426d80038d9222db3c4534cb6021a74b237d3ff97ffd33b6600dd/coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e", size = 225219, upload-time = "2026-08-06T13:49:19.293Z" }, { url = "https://files.pythonhosted.org/packages/2b/c4/dc5d2ac8f9142e7ec7de66e7bf0591db29d78955a040bd915870d9c0e657/coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9", size = 222604, upload-time = "2026-08-06T13:49:21.279Z" }, { url = "https://files.pythonhosted.org/packages/70/39/33e63df81fe2ee100897451841c821467635923e58e37c6bd4b46dd8106c/coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a", size = 222944, upload-time = "2026-08-06T13:49:23.187Z" }, { url = "https://files.pythonhosted.org/packages/99/1f/ef3ffb5557febc75a0d97aa459d0266d7d741110265121cc6d8539343d44/coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54", size = 254050, upload-time = "2026-08-06T13:49:25.008Z" }, { url = "https://files.pythonhosted.org/packages/6f/f5/1f0f6f77698c3601ca0ae7431e34b24c62ca2f06fecb23b73ed1f651d2be/coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb", size = 256967, upload-time = "2026-08-06T13:49:26.896Z" }, { url = "https://files.pythonhosted.org/packages/03/7a/2ed9bed79925f4367c83c77f66a89e5ca7229c288d2d19ad5f36d1ca0070/coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed", size = 258587, upload-time = "2026-08-06T13:49:28.692Z" }, { url = "https://files.pythonhosted.org/packages/45/8c/fa34044f71b7cc4ecb6da9c2408770959b0591fa9b5fb6fb6bca38f94298/coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce", size = 260785, upload-time = "2026-08-06T13:49:30.472Z" }, { url = "https://files.pythonhosted.org/packages/4f/54/d5727ce36b4524a7394ab9f5f1df378e1f23affcdab01037dc8655185cc7/coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c", size = 254545, upload-time = "2026-08-06T13:49:32.271Z" }, { url = "https://files.pythonhosted.org/packages/dc/e6/6e3783e576719590194bdffb6dd6d85490801785b7c331e35a245d8cb8b5/coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced", size = 256682, upload-time = "2026-08-06T13:49:34.089Z" }, { url = "https://files.pythonhosted.org/packages/dc/f2/bacdbde18b69ed2de424fcf64d9fb0a4913753d4f0eca8bae9daad69f4bd/coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800", size = 254560, upload-time = "2026-08-06T13:49:36.052Z" }, { url = "https://files.pythonhosted.org/packages/6c/a3/1fb927196e3477c1b48831169ab58ba08f451ba87ae311ff1de68b26a616/coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3", size = 258792, upload-time = "2026-08-06T13:49:38.01Z" }, { url = "https://files.pythonhosted.org/packages/41/58/30d4c149c69053de0edfe325614c1d28d508f62b1783e0e4a234d2e49136/coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768", size = 253968, upload-time = "2026-08-06T13:49:39.934Z" }, { url = "https://files.pythonhosted.org/packages/89/e4/77f639371b918aad30dda4051f95404b43578f7f2e2f87ba73e02ed1ff37/coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753", size = 255893, upload-time = "2026-08-06T13:49:41.825Z" }, { url = "https://files.pythonhosted.org/packages/5c/62/13be29b3ddab35f14c87967a4820a05106d2a3eccb4fa4ff550bf30b75e0/coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2", size = 224768, upload-time = "2026-08-06T13:49:44.08Z" }, { url = "https://files.pythonhosted.org/packages/a1/70/af0c6be0f964af6954f6b74bc109b0dbca02824696d2520fb17fe1ab06e3/coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809", size = 225242, upload-time = "2026-08-06T13:49:45.899Z" }, { url = "https://files.pythonhosted.org/packages/4f/2d/f3bd3aab899fc9efc18b53133ee68f5f98574ef480649b23e12962226387/coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d", size = 224674, upload-time = "2026-08-06T13:49:48.322Z" }, { url = "https://files.pythonhosted.org/packages/f5/ca/f69251cd63eabc6438321aea22148754cce758a26bde07dd490e3fe7cfc5/coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d", size = 223333, upload-time = "2026-08-06T13:49:50.293Z" }, { url = "https://files.pythonhosted.org/packages/a7/a7/037b53b2885b0d8447064432491a4d5a1014cd9f97a594d53acd0c04541a/coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26", size = 223630, upload-time = "2026-08-06T13:49:52.637Z" }, { url = "https://files.pythonhosted.org/packages/80/4f/152b8a4779ae90da11bb24f7467df8a59f0be48a5c52acb856325ca48289/coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645", size = 264489, upload-time = "2026-08-06T13:49:54.52Z" }, { url = "https://files.pythonhosted.org/packages/10/2d/84b4b9e0e1dd6528a51920ff7031f35b789382e467a28ec6a5a578cb8812/coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a", size = 267567, upload-time = "2026-08-06T13:49:56.721Z" }, { url = "https://files.pythonhosted.org/packages/53/fc/ba01cc25299f9f8a2c8b02d3b28c53f3543d9fbfbe4e74fa2760b48f163e/coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624", size = 270123, upload-time = "2026-08-06T13:49:58.736Z" }, { url = "https://files.pythonhosted.org/packages/cf/d0/db2647cbf40b14f8c308f94ff7bf89c06d564e59f396906edf50086ec788/coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa", size = 271107, upload-time = "2026-08-06T13:50:00.811Z" }, { url = "https://files.pythonhosted.org/packages/70/ff/4d2d17924552c458bb4f77dd631f0e3bc92fbbdf2d2d916cd4b33bbfd5b1/coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f", size = 264955, upload-time = "2026-08-06T13:50:03.023Z" }, { url = "https://files.pythonhosted.org/packages/ee/de/dc010c7a3691f396d93bbc26bfcafa1c2a3a351cd520470f15faf5795bd5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f", size = 267949, upload-time = "2026-08-06T13:50:05.557Z" }, { url = "https://files.pythonhosted.org/packages/78/ea/dc96a11375e83c045c2f7c61fb6918277cfe9401db7c0f7b1d111a84b2e5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67", size = 264421, upload-time = "2026-08-06T13:50:07.612Z" }, { url = "https://files.pythonhosted.org/packages/c8/86/b77131a0f9503ce461cd577076147d7a9040f0c5dda772686f729e2cc9cb/coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc", size = 269121, upload-time = "2026-08-06T13:50:09.58Z" }, { url = "https://files.pythonhosted.org/packages/24/24/944bc35007862955e7ebf05754e645419dcf5d7526c52735cfa2715e8ebf/coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88", size = 264565, upload-time = "2026-08-06T13:50:11.722Z" }, { url = "https://files.pythonhosted.org/packages/c7/cc/a3bb9f93e7e740659163e2ea584f8196ddcd2c456a5dbe15f6c50105fec1/coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc", size = 266522, upload-time = "2026-08-06T13:50:13.786Z" }, { url = "https://files.pythonhosted.org/packages/49/dd/e0e40f3560d878d888c580698ff5ad1179f5e1c3ac949684ef66b41a3817/coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a", size = 225068, upload-time = "2026-08-06T13:50:15.825Z" }, { url = "https://files.pythonhosted.org/packages/c6/7e/37732ea80eebc30e976e4cdab15c190bc42d96959a42e38ddf6f8c60468f/coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b", size = 225895, upload-time = "2026-08-06T13:50:17.928Z" }, { url = "https://files.pythonhosted.org/packages/c6/08/1e00f7923eaaba45fb3d51dd794125fc766304b1df264f3a9c6557bfb30e/coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278", size = 225213, upload-time = "2026-08-06T13:50:19.981Z" }, { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "cryptography" version = "50.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, { url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, { url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, { url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, { url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, { url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, { url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, { url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, { url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, { url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, { url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, { url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, { url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, { url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, { url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" }, { url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" }, { url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" }, { url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" }, { url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" }, { url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" }, ] [[package]] name = "distlib" version = "0.4.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] [[package]] name = "filelock" version = "3.32.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6d/30/03b03951873a1a0ffc7e8ca0e10c15597b59e8d0e39260704cd2ea087bc4/filelock-3.32.4.tar.gz", hash = "sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30", size = 222126, upload-time = "2026-08-23T17:37:55.363Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl", hash = "sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd", size = 99864, upload-time = "2026-08-23T17:37:53.913Z" }, ] [[package]] name = "flask" version = "3.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "click" }, { name = "itsdangerous" }, { name = "jinja2" }, { name = "markupsafe" }, { name = "werkzeug" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, ] [package.optional-dependencies] async = [ { name = "asgiref" }, ] [[package]] name = "flask-caching" version = "2.5.1" source = { editable = "." } dependencies = [ { name = "cachelib" }, { name = "flask" }, ] [package.dev-dependencies] dev = [ { name = "asgiref" }, { name = "flask", extra = ["async"] }, { name = "mypy" }, { name = "pallets-sphinx-themes" }, { name = "pre-commit" }, { name = "pre-commit-uv" }, { name = "pylibmc" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-xprocess" }, { name = "redis" }, { name = "ruff" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-intl" }, { name = "sphinx-issues" }, { name = "sphinx-tabs" }, { name = "sphinxcontrib-log-cabinet" }, { name = "tox" }, { name = "tox-uv" }, { name = "types-redis" }, { name = "types-uwsgi" }, ] docs = [ { name = "pallets-sphinx-themes" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-intl" }, { name = "sphinx-issues" }, { name = "sphinx-tabs" }, { name = "sphinxcontrib-log-cabinet" }, ] docs-auto = [ { name = "sphinx-autobuild" }, ] gha-update = [ { name = "gha-update", marker = "python_full_version >= '3.12'" }, ] pre-commit = [ { name = "pre-commit" }, { name = "pre-commit-uv" }, ] tests = [ { name = "asgiref" }, { name = "flask", extra = ["async"] }, { name = "pylibmc" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-xprocess" }, { name = "redis" }, ] tox = [ { name = "tox" }, { name = "tox-uv" }, ] typing = [ { name = "mypy" }, { name = "pyright" }, { name = "pytest" }, { name = "types-redis" }, { name = "types-uwsgi" }, ] [package.metadata] requires-dist = [ { name = "cachelib", specifier = ">=0.17.0" }, { name = "flask", specifier = ">=3.0" }, ] [package.metadata.requires-dev] dev = [ { name = "asgiref" }, { name = "flask", extras = ["async"] }, { name = "mypy" }, { name = "pallets-sphinx-themes" }, { name = "pre-commit" }, { name = "pre-commit-uv" }, { name = "pylibmc" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-xprocess" }, { name = "redis" }, { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-intl", specifier = ">=2.3.1" }, { name = "sphinx-issues" }, { name = "sphinx-tabs" }, { name = "sphinxcontrib-log-cabinet" }, { name = "tox", specifier = ">=4.39" }, { name = "tox-uv" }, { name = "types-redis", specifier = ">=4.6.0.20241004" }, { name = "types-uwsgi", specifier = ">=2.0.0.20260408" }, ] docs = [ { name = "pallets-sphinx-themes" }, { name = "sphinx" }, { name = "sphinx-intl", specifier = ">=2.3.1" }, { name = "sphinx-issues" }, { name = "sphinx-tabs" }, { name = "sphinxcontrib-log-cabinet" }, ] docs-auto = [{ name = "sphinx-autobuild" }] gha-update = [{ name = "gha-update", marker = "python_full_version >= '3.12'" }] pre-commit = [ { name = "pre-commit" }, { name = "pre-commit-uv" }, ] tests = [ { name = "asgiref" }, { name = "flask", extras = ["async"] }, { name = "pylibmc" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-xprocess" }, { name = "redis" }, ] tox = [ { name = "tox", specifier = ">=4.39" }, { name = "tox-uv" }, ] typing = [ { name = "mypy" }, { name = "pyright" }, { name = "pytest" }, { name = "types-redis", specifier = ">=4.6.0.20241004" }, { name = "types-uwsgi", specifier = ">=2.0.0.20260408" }, ] [[package]] name = "gha-update" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "httpx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/e8/eb710e08998a22b314cc068f14805cfdd12e3934a5496c8916c5a164c65a/gha_update-0.2.0.tar.gz", hash = "sha256:328ee0db09346ad13ee90646698cea2ec1f9035964ddd7c2a728a91034c3f4b0", size = 4756, upload-time = "2025-07-14T03:13:33.254Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4b/29/a0e42b0b80d614aa82929f65cce0d51443eea296802b2094cadc5660321b/gha_update-0.2.0-py3-none-any.whl", hash = "sha256:ec5641bf23f71baa1232fc61b3059fb08456e1b78150d1e9c1bab69b37046e49", size = 5323, upload-time = "2025-07-14T03:13:31.932Z" }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "identify" version = "2.6.19" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] [[package]] name = "idna" version = "3.19" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] name = "imagesize" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "itsdangerous" version = "2.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "librt" version = "0.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/36/9b/356320fbae2ac8467e21c5e73e1389c80468e4998c62cc7d3536cc51b614/librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162", size = 214338, upload-time = "2026-08-07T10:49:42.663Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/59/52/06790ced2ac7117f890c21bda43c39c958ec82aa665c0718e821d33ff939/librt-0.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:823b92cf3c18ecd08afc70c42473888b41b6e8ef5046f3b82c05c154a2fa3d22", size = 148039, upload-time = "2026-08-07T10:46:41.165Z" }, { url = "https://files.pythonhosted.org/packages/e7/1d/8e150b7fc449a1f33c8a760965cc1f43b14fc1577d9d0b50ab2701420e74/librt-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70bc1b602cf59917e8f0c7a2cbc8bcc6fbc14d5486136b00707a79619121d63", size = 153067, upload-time = "2026-08-07T10:46:42.418Z" }, { url = "https://files.pythonhosted.org/packages/51/87/a162bc5a66a35599dc619ecb215145f4de7d68e886b479b6d12593139f7c/librt-0.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:814ff83a25b5fce8b9c80c4dd803153fb5c5599fc74db9e022466938368957ef", size = 493087, upload-time = "2026-08-07T10:46:43.657Z" }, { url = "https://files.pythonhosted.org/packages/e5/3a/aeea1fc620cf48060d3065b37614edbf97043c099d0f50782bc8ca61d897/librt-0.15.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:57f5eeb6ad4c180de583b1038e61fe5fbd9796bb69a8a1c1a0c7ddbec4c8c60f", size = 485608, upload-time = "2026-08-07T10:46:45.038Z" }, { url = "https://files.pythonhosted.org/packages/52/ff/fe571ad416f0856fd0d5578ffc2e6dc531891e586e36b647bcf50569cab8/librt-0.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82909c8f7eb9952656b65d3147afde4cf8e6d5a991eebc86418b5e65843b0ab8", size = 498723, upload-time = "2026-08-07T10:46:46.35Z" }, { url = "https://files.pythonhosted.org/packages/0f/e1/7a65eb5dedb1f00aebd948cdd8e17add48bf066cab3514e9daf84ab45a6c/librt-0.15.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f779070399f991400fc451719e0ea388eb7de313388bada2c127a35de05f798a", size = 516002, upload-time = "2026-08-07T10:46:47.599Z" }, { url = "https://files.pythonhosted.org/packages/5f/45/59832b0ebfbd08c2742e6ece372ceb53f18bf1faef5d33c8daf3abebf749/librt-0.15.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bac89069bc496ebdf4f79ebb57bbd10d0b214c8454225deb672d91002bd17e18", size = 508607, upload-time = "2026-08-07T10:46:48.873Z" }, { url = "https://files.pythonhosted.org/packages/ea/0d/37fa73f3b43ebd8259f91ae9102a15e5a54e65d581e48dea72df3e81d7a4/librt-0.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e0d00c708fb2f5822b152429b1ac80a58dbbbc3f6c232c4d13a3f7fcf2ea5b4c", size = 530422, upload-time = "2026-08-07T10:46:50.45Z" }, { url = "https://files.pythonhosted.org/packages/26/02/e046c6fe7a5881ac34623242192f484426ba8a75595fd18f22c53a3f530f/librt-0.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c6624fe268625869485553dd7cc1daf30d22558215bb2a4ff16f67a9801a31a", size = 534303, upload-time = "2026-08-07T10:46:51.693Z" }, { url = "https://files.pythonhosted.org/packages/95/32/d5e6d861ab0366f3edf74f887ab0c9eb9f535aaf01d32b80b4f734daa179/librt-0.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f56b397858a23dacf35ede366ed2212fdc03a6a57a1ad36468ad6e9dc5fac091", size = 536084, upload-time = "2026-08-07T10:46:52.951Z" }, { url = "https://files.pythonhosted.org/packages/2a/de/d69d725513fe53fc90c6d7a1f86e4428939bad2fb905b17fe4c18d413dde/librt-0.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4388184646efe2054911c5b00a1077d6d1ee86a95b7e8ba96dc7850a809f3f40", size = 514307, upload-time = "2026-08-07T10:46:54.194Z" }, { url = "https://files.pythonhosted.org/packages/36/93/f8aded0d6682b4f25820fa86e0690f87f01df9fd7bd09ddb04d9167ad021/librt-0.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97335f59082f9fe2ce6c2a9cc6433a0114bbb6cd4d5c09dd76c95c68b9f9a8b0", size = 557686, upload-time = "2026-08-07T10:46:55.443Z" }, { url = "https://files.pythonhosted.org/packages/74/09/ffeb6bdeb6cd862b4272fddc8ad05f938dd25d020ed517e631813917d80a/librt-0.15.0-cp311-cp311-win32.whl", hash = "sha256:83380ffde38062a2e9bb55d83e74474f6614665528b98a6928720fc006dfffbb", size = 104917, upload-time = "2026-08-07T10:46:56.605Z" }, { url = "https://files.pythonhosted.org/packages/96/28/7e2313a3ffbf0b4de7ba3da58a09e488507b4bd1ea2b5e69378354a23415/librt-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:f75720477ee05d509a310e856cacc8d909adc182f7b91193c207bcc26d7ee6db", size = 125886, upload-time = "2026-08-07T10:46:57.729Z" }, { url = "https://files.pythonhosted.org/packages/39/9e/04b8c3cde014ef255ee785730425268354543acc38902093a40afa0dc164/librt-0.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:256237037a3ab001ae8d9803b2d43562a4c3aa38739843694349e4d5ebb0fd56", size = 111885, upload-time = "2026-08-07T10:46:58.787Z" }, { url = "https://files.pythonhosted.org/packages/ba/39/99c25030e782bdfb7a21be8c05254806a2e4bbb05c8d50c2a2130acbfa05/librt-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e87bc679f86a99aa3b26e3c78eeb821a247c9a28eae48eaafcc32c3bf4c3bb9e", size = 151021, upload-time = "2026-08-07T10:47:00.057Z" }, { url = "https://files.pythonhosted.org/packages/14/43/f4b1bd1b2888798a1409808889a25ea1ba49eaabce7d681ed27734c2df9d/librt-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71599e011ac880e8e45d46047d714871894c7d4ab6f25626f8d4f89da21f368d", size = 155267, upload-time = "2026-08-07T10:47:01.311Z" }, { url = "https://files.pythonhosted.org/packages/0c/db/3ad9c965c72f1e1d6beeec44ec10a54e17be8ae042fbb4baade16cbadced/librt-0.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c802434092b769b1d613ed2e13fac15fbfce1934a74bd10283b03c0fae231cd1", size = 503136, upload-time = "2026-08-07T10:47:02.45Z" }, { url = "https://files.pythonhosted.org/packages/4b/07/5888a6d76acd62ebce66c61b74d94e9370b9c32929f111e487bb6546f8ed/librt-0.15.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5500eeae393a184d14e1f35645962c27129d20c81afa4069e6ef826ebc2b3aaa", size = 496670, upload-time = "2026-08-07T10:47:03.675Z" }, { url = "https://files.pythonhosted.org/packages/29/39/ab57cc2f5b276156da02bb7f5a8921bada1cb1993ffec99acf811c602c23/librt-0.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ecfc32dfb46fb7b565bcd6abf9412acf978775a998273d22888a6d7953730dd", size = 513688, upload-time = "2026-08-07T10:47:04.981Z" }, { url = "https://files.pythonhosted.org/packages/a7/b9/bdbb0b648b5c2befb031f4c6f3b1dd857415e8fb492a25a3c764a6681e6c/librt-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cc46cfd15022e35084355478c9ac809d90b1152222706ac9a7655ec21df6fa", size = 531904, upload-time = "2026-08-07T10:47:06.211Z" }, { url = "https://files.pythonhosted.org/packages/93/26/473c2e4b6c104e9e58e27ce95fc8005c8bd4fc36cae4f254371125a92db8/librt-0.15.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5f51401d102c885b9ca509e62c79b1dbff286e1b9b047fde6f763780789356d", size = 524427, upload-time = "2026-08-07T10:47:07.592Z" }, { url = "https://files.pythonhosted.org/packages/26/60/03b3abb82b41714671b907bf6989b228e31e6a8af52dec82b5b0728dc250/librt-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cc30523e3f1a23fb7511cc659834a0d01a1042bb9de359bc1c131cc4ec6c9656", size = 543155, upload-time = "2026-08-07T10:47:08.866Z" }, { url = "https://files.pythonhosted.org/packages/f2/0e/9bb1f0a4affbd0a1888f4f79dc03ed2a299d9a2c26c59ab2a97dcbf11903/librt-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59fe030d8ae4a57e3fb7756bf35a858de74e04066fc8555c53d0af979132af81", size = 546890, upload-time = "2026-08-07T10:47:10.327Z" }, { url = "https://files.pythonhosted.org/packages/dc/84/6937a280d461f7de6e031ffb02edc2b7c3c90d49d630565ce8ff27cbc5f2/librt-0.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a6526a2a956bbb1e4ae3568c82e650fc99119c66bb011ea60715744955a2b4d", size = 555163, upload-time = "2026-08-07T10:47:11.798Z" }, { url = "https://files.pythonhosted.org/packages/bc/95/2a2853c1ee014bf102116e7f897a04beeaeb2461b45b79af98bdfb95f1ef/librt-0.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:85ea21ec6730194d67156b0e0b5430ccb1d61f8b8b907e39b37f9812b74a13f0", size = 535812, upload-time = "2026-08-07T10:47:13.279Z" }, { url = "https://files.pythonhosted.org/packages/c9/4c/cf9601c1b4c5f09280acd5d83abdb2e68527a2be8257136eb42304218622/librt-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e47b8ba865d7ede071a91a7163073bbaeb72541f1ef8a07d512c45c7b5007f2", size = 573688, upload-time = "2026-08-07T10:47:14.727Z" }, { url = "https://files.pythonhosted.org/packages/47/6d/9ac7cbec46189a7625af4b5acbd25f10d827f4141b2002181848c8418923/librt-0.15.0-cp312-cp312-win32.whl", hash = "sha256:a5207ec414d1c4a2a7231b2086970dc036f94293cdf338190984958a013a42f1", size = 106138, upload-time = "2026-08-07T10:47:15.973Z" }, { url = "https://files.pythonhosted.org/packages/38/d0/2ae99c83be86ce23f925ac1aeeedc777e97f427c4a8d190c70d0a16e9a87/librt-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:73b30cfa976659b3917c8f6153bdb0591c6a9ec6583599fd24a689b690622022", size = 126974, upload-time = "2026-08-07T10:47:17.049Z" }, { url = "https://files.pythonhosted.org/packages/5d/ef/dd24f9635c730b86b87587967dda7516b1845e8b17684603d31607fed598/librt-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:a54cf9e0ef47b96af580849db5471142200568ce1e02cbf416addab551369570", size = 112292, upload-time = "2026-08-07T10:47:18.222Z" }, { url = "https://files.pythonhosted.org/packages/e7/42/467b53a601b406ccd7b97c1fd54b59cb34f9185ad5ce7e9d5c3c4e8961c8/librt-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:db13ca398005abcbe538deda87b686d9bd08b7001cf40c4c06b444960ae10a26", size = 151029, upload-time = "2026-08-07T10:47:19.312Z" }, { url = "https://files.pythonhosted.org/packages/3e/e6/36c2299b7a94b84fdd01220d8a777a71be5be0925bb0dbdf71c0a06a34d9/librt-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa1f1995789dca3698bc550aaceb09a51bd5df0a057ff84ff15296cd1975b801", size = 155194, upload-time = "2026-08-07T10:47:20.398Z" }, { url = "https://files.pythonhosted.org/packages/c9/b6/ed5071f9325845e670bd36012757419767fbf56af77ed483077b9e4db541/librt-0.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55456ea87d8df21808446d03817be2f65e20391c1c615d9187440dff28cd08dc", size = 502568, upload-time = "2026-08-07T10:47:21.652Z" }, { url = "https://files.pythonhosted.org/packages/7f/81/6450c67c3615d87704bcbc21323fafc69c799b06a044c447529f725d4b01/librt-0.15.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5a86a5a08c2235316bdb359d5dbb6ce0abfca7fac06363103e2c5af571d92f95", size = 496153, upload-time = "2026-08-07T10:47:22.925Z" }, { url = "https://files.pythonhosted.org/packages/e1/d6/5f52b722bc75076954b3bfd49be15ea362df4d580c6fb315d0f617100d30/librt-0.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56b6a368529bed262da40ce13f8fef590db0479819cca84f16a1f01ac356d0b", size = 513336, upload-time = "2026-08-07T10:47:24.213Z" }, { url = "https://files.pythonhosted.org/packages/8d/e2/c08fd1d36ce63ea5a12b85c5d37f4550b5f86a692167e41e5a74222607ae/librt-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234d8d394721fa0d786af15ebf1f3fb7f3ed82fd1cd0cde45c2f247b5d4281d2", size = 531661, upload-time = "2026-08-07T10:47:25.507Z" }, { url = "https://files.pythonhosted.org/packages/3f/d8/d9482fcbeb177b9eb87bb3899eeb3b42be690313c652f9e146b1d0681fb2/librt-0.15.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8363d7accb0286ac3a0e633f396e93800dafb8150494505daf9515bbda591f3", size = 524487, upload-time = "2026-08-07T10:47:26.79Z" }, { url = "https://files.pythonhosted.org/packages/10/cc/075171517b41f861753034fbb151b42cfc83bcc853849f24f5e66fd60ccf/librt-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0f0ee3644d951f31055ad07d77d92520e84505dd7a432cc4cd501dd70ee06785", size = 543201, upload-time = "2026-08-07T10:47:27.999Z" }, { url = "https://files.pythonhosted.org/packages/b0/03/42c2330f37eeb475b6affeedd06518f60035f323af3a839335e3fc9fef2d/librt-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cfd1a81a648806e6a7717be4cc4d1bb392fa229752bf8444ba365e381e984d6", size = 546467, upload-time = "2026-08-07T10:47:29.396Z" }, { url = "https://files.pythonhosted.org/packages/57/1e/1ad4c5638f7e64d8560328bd25c54b409a661bdb6ff254b38ff90744288d/librt-0.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6cd22c9da0d866558e46a041f1cc0c2bbb26b61b137b2347fa834c332e1d101", size = 555139, upload-time = "2026-08-07T10:47:30.815Z" }, { url = "https://files.pythonhosted.org/packages/49/41/39fa7d15db1204cd1cbe6514680fbdc243adf754a0885061308f43afc013/librt-0.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6d5225ef8801e4ea5e482fa9b5dfb891dd9ef6f6d870f1f25d449ca2c70ac218", size = 536050, upload-time = "2026-08-07T10:47:32.222Z" }, { url = "https://files.pythonhosted.org/packages/1e/88/c6dcf0dd8e26dc0c9a499a2abab8646c86dcaf9ecea9524cb46d3686331a/librt-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d28a05796b99f749bf8794f17ba9ba1612d0076b802e9cfc62c554634e9ce3b", size = 573700, upload-time = "2026-08-07T10:47:33.527Z" }, { url = "https://files.pythonhosted.org/packages/1b/9b/ab54c71a7918a7c34fa5327fb61390a77446a07a146fbfb1165250a61035/librt-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2067ff438048cead9d223ca5675bae2a25e520a7c3e6c1498bf9c6892d22caab", size = 82194, upload-time = "2026-08-07T10:47:34.835Z" }, { url = "https://files.pythonhosted.org/packages/8d/b2/4f9a243bb892395f3becb80789ade13771701091f9f07ab8230247953ba8/librt-0.15.0-cp313-cp313-win32.whl", hash = "sha256:1cd3b721f24c206398b9e26da3c3a9c011e6e89d06f318ba8ebefc30f1003890", size = 106231, upload-time = "2026-08-07T10:47:36.251Z" }, { url = "https://files.pythonhosted.org/packages/bf/af/64aff4885a40b93132382f2c314647d722574605416504379184ef3045ea/librt-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:f395a4a9a03ac062dbe9a9f82e0c720502e590a38feee6a757bc82e9c63afbd8", size = 126996, upload-time = "2026-08-07T10:47:37.453Z" }, { url = "https://files.pythonhosted.org/packages/27/83/335bccf6c7cb9028cb0b54aead27d9ece3f01f83bc6baa2abace5da655c1/librt-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:0a15cb554761247d84a3ec0cbdf4078d70725384f0e4662c0fa3b26266eb60ad", size = 112188, upload-time = "2026-08-07T10:47:38.729Z" }, { url = "https://files.pythonhosted.org/packages/a8/93/949053fb462eecc4a9a5ee770a81f4b40be7b79538b245545d4aebc6b58b/librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993", size = 149833, upload-time = "2026-08-07T10:47:39.86Z" }, { url = "https://files.pythonhosted.org/packages/61/ca/8281aa6cd560a3420e4497729f6b704b53be3eeaaef82d5aeadddaf7441f/librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8", size = 154088, upload-time = "2026-08-07T10:47:41.065Z" }, { url = "https://files.pythonhosted.org/packages/dd/02/1a1662dceaba6a086360891448d5ce9a7d3555976cae59a31a39d744b9c7/librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21", size = 494215, upload-time = "2026-08-07T10:47:42.388Z" }, { url = "https://files.pythonhosted.org/packages/69/84/99211619dc656370a3740c33d2b0b6d5a3fb1e73689314f6ed477a397dc4/librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953", size = 491173, upload-time = "2026-08-07T10:47:43.683Z" }, { url = "https://files.pythonhosted.org/packages/d4/aa/5448d0b05f4579b635d3899176817ebf561af0e57bacd425b5b1887264c1/librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa", size = 505512, upload-time = "2026-08-07T10:47:45.314Z" }, { url = "https://files.pythonhosted.org/packages/95/82/01940e40b83c43a546c4a3c896cf34ca272a9690899d55914e4827b3dcce/librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879", size = 523073, upload-time = "2026-08-07T10:47:46.66Z" }, { url = "https://files.pythonhosted.org/packages/88/fa/759c0030f3ee371439eb26de34fc745807caf0abb878af7af4b8b7c3dd3d/librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae", size = 515080, upload-time = "2026-08-07T10:47:48.319Z" }, { url = "https://files.pythonhosted.org/packages/0b/27/894e072228fcb159703c655da69f8cd10dbed489c36e3df7dd032a2483be/librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd", size = 534164, upload-time = "2026-08-07T10:47:49.875Z" }, { url = "https://files.pythonhosted.org/packages/98/a3/0078e91c1f36f8815db17827de15650b9a3fe56c55fbf998c854b34e40d3/librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285", size = 540616, upload-time = "2026-08-07T10:47:51.408Z" }, { url = "https://files.pythonhosted.org/packages/86/33/81a29b796dd52a45e9ef7974c7732926e8f10f15b8d2be505665979f896d/librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239", size = 545890, upload-time = "2026-08-07T10:47:52.818Z" }, { url = "https://files.pythonhosted.org/packages/05/82/8be1baa1350e5d30cfd70ae79d0a6f4dc5862ef47f7bb2808aabc9bb86e5/librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60", size = 523287, upload-time = "2026-08-07T10:47:54.165Z" }, { url = "https://files.pythonhosted.org/packages/c6/4f/d1be6a01a35c20ef734e0e44113f87d4af756a9354a89dcfbe3b4f8af5e1/librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65", size = 565868, upload-time = "2026-08-07T10:47:55.566Z" }, { url = "https://files.pythonhosted.org/packages/67/88/649cfa33f5825927b160610f670bdab012a64d627eddb94fa795ea4292fd/librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622", size = 81619, upload-time = "2026-08-07T10:47:56.886Z" }, { url = "https://files.pythonhosted.org/packages/22/31/8e88a8d5e48fc8d1a817787fb6811dfff6499acd6c8683dd83934aa6ede0/librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15", size = 100138, upload-time = "2026-08-07T10:47:58.093Z" }, { url = "https://files.pythonhosted.org/packages/80/92/20fd6c4b6a1b1a564b076d55cd3d427d8428217d7638dc25a654cc4791d4/librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28", size = 121258, upload-time = "2026-08-07T10:47:59.564Z" }, { url = "https://files.pythonhosted.org/packages/fc/28/6af430b44d9ebb897b865a3c363b6dcace51357be2347cc0f8f869656a86/librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95", size = 106467, upload-time = "2026-08-07T10:48:01.097Z" }, { url = "https://files.pythonhosted.org/packages/7e/aa/b42bb798942ced219f6d63b27e07f91237887a8d0bd0921666db79a13790/librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714", size = 159523, upload-time = "2026-08-07T10:48:02.442Z" }, { url = "https://files.pythonhosted.org/packages/75/03/1b53cd4ef904e73b1d828a5f90143bf94a2967d7cfff0b9ccf93e12aa9b4/librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3", size = 161638, upload-time = "2026-08-07T10:48:03.725Z" }, { url = "https://files.pythonhosted.org/packages/ac/c4/9f9c9fba097d49e9e694c2b4dc331df31884645ecbc58a93b4b5fc69d2c5/librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d", size = 701795, upload-time = "2026-08-07T10:48:05.135Z" }, { url = "https://files.pythonhosted.org/packages/4c/05/0966840bda0380c8ae167b9043c6230202941cc90ea29c48e096964c765e/librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38", size = 682147, upload-time = "2026-08-07T10:48:06.555Z" }, { url = "https://files.pythonhosted.org/packages/18/af/1c47ca573c30ea47d195aec26133af522fea1104afaace028d7b32247ea8/librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19", size = 696397, upload-time = "2026-08-07T10:48:08.03Z" }, { url = "https://files.pythonhosted.org/packages/2e/0f/1aed6223d4f9f9d1171a8596ff100ea4c3f7699fea7a4ba657c3e60daa6c/librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab", size = 722542, upload-time = "2026-08-07T10:48:09.569Z" }, { url = "https://files.pythonhosted.org/packages/c6/22/9e3a929aea456c97d69e6ef3884efea56d4807f97399471cc946baebd8af/librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2", size = 729709, upload-time = "2026-08-07T10:48:11.129Z" }, { url = "https://files.pythonhosted.org/packages/e9/1b/c327ef6018e3a9ca0b8e7c5eddeeb331ba8f9b76c24e126d37d0f6d62faf/librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108", size = 752891, upload-time = "2026-08-07T10:48:12.558Z" }, { url = "https://files.pythonhosted.org/packages/d7/d1/d5f1ea02c56930087009e39db9b70660a663e76c730b27b925d786718457/librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08", size = 745301, upload-time = "2026-08-07T10:48:14.55Z" }, { url = "https://files.pythonhosted.org/packages/d9/3c/5f7c585d15ebb2250c73e7c0ee4e9e47be72c65d520c07ddbcdc62037674/librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47", size = 747921, upload-time = "2026-08-07T10:48:16.453Z" }, { url = "https://files.pythonhosted.org/packages/7f/52/1443a446486eba966bcbca1696b472e4f210320ec42f490a47f48fbf0fdc/librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81", size = 727561, upload-time = "2026-08-07T10:48:18.089Z" }, { url = "https://files.pythonhosted.org/packages/79/91/2270a9380f11725cf83ce1925a5e32dd1dde2be9bba597f25c10a38644e7/librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc", size = 774417, upload-time = "2026-08-07T10:48:19.611Z" }, { url = "https://files.pythonhosted.org/packages/9e/3b/f4b1548d4f5b99186737fe27aec238e9823e8d5d23bf4df007c030689dc5/librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf", size = 104381, upload-time = "2026-08-07T10:48:21.048Z" }, { url = "https://files.pythonhosted.org/packages/80/b6/134afad262def1de04c0843c376d02135f1168af43f22e09a52bd8394727/librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915", size = 127034, upload-time = "2026-08-07T10:48:22.561Z" }, { url = "https://files.pythonhosted.org/packages/99/5f/1b6846b20572bd699c9e9ec321a5f781845bee477df2aa2a43b28bc40119/librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605", size = 110827, upload-time = "2026-08-07T10:48:23.804Z" }, { url = "https://files.pythonhosted.org/packages/c6/44/4de9f4ddadb009a55c7758eb5736d62534a7daaf27bd71bc50e64b606b06/librt-0.15.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8443e38dcfcfdbcf5add5118c623efd788d65ac2e25756d6251a54a06a4d0aca", size = 149843, upload-time = "2026-08-07T10:48:25.148Z" }, { url = "https://files.pythonhosted.org/packages/1f/eb/5d9ab71e30119c44094e0275f38b47dd327aea0f843a080396677029d508/librt-0.15.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6d15a29033c57490cfe2069097c6fc4049e4e65ffbb749be7dc453b7c4c68965", size = 154510, upload-time = "2026-08-07T10:48:26.485Z" }, { url = "https://files.pythonhosted.org/packages/d0/9c/8505d1b8f5e8c19587bd03f7429993b3e9ce5c06819d856bfb11d919374c/librt-0.15.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c05c729b589e734c09578bf5964be48a911765484840d017bbc84f49d4c4ad", size = 497543, upload-time = "2026-08-07T10:48:28.045Z" }, { url = "https://files.pythonhosted.org/packages/1d/9a/3a8390775cb095765aded027ac9c63e7c8ea74e731498607544c6505de0e/librt-0.15.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fa60887537e1d0cd2d9982269d33a709bf54b195cd2b9364fc0a758022af5bd9", size = 480452, upload-time = "2026-08-07T10:48:29.531Z" }, { url = "https://files.pythonhosted.org/packages/e7/40/258a4a7117ee915d66de5cd9b8ade65a440993161107ce3a686f1859955c/librt-0.15.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8bc24219b24c0af375718942ab75e3544b2763085f40f965be4326734ae8328", size = 507768, upload-time = "2026-08-07T10:48:31.007Z" }, { url = "https://files.pythonhosted.org/packages/6b/c6/2f4dd296c97a0b85b98894519b279408ec9dd602d4f692b1ea0e25dee670/librt-0.15.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86a21a7bd3fe3a419512ef424cc1c020f6771d0b29cfddff36d1635a855e63f0", size = 525122, upload-time = "2026-08-07T10:48:32.7Z" }, { url = "https://files.pythonhosted.org/packages/49/dd/29eab42be13b2bf0ea8cb227135a45d44693e30a7e8b92871981ff56b82b/librt-0.15.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbab647e88d90b3167b91efe7091e248653688ed4337e4f90907a722c7361bb9", size = 520371, upload-time = "2026-08-07T10:48:34.294Z" }, { url = "https://files.pythonhosted.org/packages/91/ed/4bad71adeca8fe208b775c2a35417fa5a2584c8f4791daaf89a89450fea1/librt-0.15.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d8edcf6f550e918dca779c069b9e156385c60b406f99fc7641f32c52f7193659", size = 537258, upload-time = "2026-08-07T10:48:35.88Z" }, { url = "https://files.pythonhosted.org/packages/4c/63/59dba6143fdcc7240c54458b629f3250000a61b8945890fc9efd451b19c5/librt-0.15.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:8b62076030baa2d8b1501a46bf0e19c27a489aa90671c55665bff7887f7660b0", size = 527432, upload-time = "2026-08-07T10:48:37.466Z" }, { url = "https://files.pythonhosted.org/packages/ec/21/21a24c6a2327d8362580efebe77286bf47b0f4062ec5ea41766e609d3c7d/librt-0.15.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:d00d20d1818e82a07a0ee0aa89a98b17ed7916b92441090b683719cb20a59b6d", size = 548108, upload-time = "2026-08-07T10:48:39.384Z" }, { url = "https://files.pythonhosted.org/packages/5a/6d/fc68c89a7971418b41f9a873623ff935cb864097544c6a2f8ce491c8ef5d/librt-0.15.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4e6ee93fc3cf848dcbf0cce2eca73d8e7dcd0cc2b6df3a529d57750b30a4c55c", size = 529681, upload-time = "2026-08-07T10:48:41.392Z" }, { url = "https://files.pythonhosted.org/packages/65/7e/c2d98766124400d722063a630b0fde38a9fc768705d37eecca15c47dc192/librt-0.15.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:32896a0af72508ea979e0acb4e4c04cbeeae04938167950d535c83c45597167d", size = 567736, upload-time = "2026-08-07T10:48:43.124Z" }, { url = "https://files.pythonhosted.org/packages/55/6c/f8c34a95e3a515c6e1c192b89511e7253c89a7760c6b500d57ffdb8d2dc8/librt-0.15.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:ec3ba415afaf951f6951b1dd16d3c8e4f540065fc382d7e70b823a79567ca374", size = 81673, upload-time = "2026-08-07T10:48:44.645Z" }, { url = "https://files.pythonhosted.org/packages/c9/9e/e23fa8e78679ec45728188650b39e8ff476c83b691c96f749217df3b1b7c/librt-0.15.0-cp315-cp315-win32.whl", hash = "sha256:d2813ba2503764f0450680c533d13df7cff9b49df1411062eded5f67db4195b9", size = 100081, upload-time = "2026-08-07T10:48:46.171Z" }, { url = "https://files.pythonhosted.org/packages/e1/dc/3eb4c5e297343f0620a55532cd7c8d764d3001fa2159212dadf480464827/librt-0.15.0-cp315-cp315-win_amd64.whl", hash = "sha256:b87d67e33afaf265262f2a66db578284b88ee2e6fcd224579cb5c15518677ad8", size = 121228, upload-time = "2026-08-07T10:48:47.631Z" }, { url = "https://files.pythonhosted.org/packages/97/70/43abce19f04e49762f8ec834c8fafee13cc40fd6b94a72a24e534febfcd0/librt-0.15.0-cp315-cp315-win_arm64.whl", hash = "sha256:713bd7df21170b982e729e46870f31d6b437bd1a9b4648cffb529bd3c2ec5c4b", size = 106487, upload-time = "2026-08-07T10:48:49.095Z" }, { url = "https://files.pythonhosted.org/packages/de/15/83f2deddb9368b8951ec8c9477269b5b9b8bd9bbf15e57402d0f38817dca/librt-0.15.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3de789c82752730f94782a5ee518baf9c05edf85733aeaf73bb6e518755cdf54", size = 159448, upload-time = "2026-08-07T10:48:50.649Z" }, { url = "https://files.pythonhosted.org/packages/06/bf/043097353f9b3c73b583d07f6b8e552795463f4bfc8caf85e42eee50c26a/librt-0.15.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e0b5deec9a8664eb722c797241970fd4aa1894d25fda36a1ddac0f7407606bd6", size = 161686, upload-time = "2026-08-07T10:48:52.174Z" }, { url = "https://files.pythonhosted.org/packages/f4/2a/8ae77f9719d42ce71cd708560a3557b38ac3c17a0383e57f87084de45bbe/librt-0.15.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5563302a8359bc2295bb7084d1a8ed1519df96afb30eb2aa4e0bff7b54228988", size = 710668, upload-time = "2026-08-07T10:48:53.782Z" }, { url = "https://files.pythonhosted.org/packages/61/34/c0436ea134deb9a0d6da80a396a2739a81cb31e0418f7227239e23140898/librt-0.15.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:22d6263b9d39d7bbb286fa791945646e3218f1be2d693e36fb630f1d0e59cd13", size = 679396, upload-time = "2026-08-07T10:48:55.645Z" }, { url = "https://files.pythonhosted.org/packages/4a/9f/001e0d99aa9250d5cd5715a9081291a20656083459f9019cda15255329e1/librt-0.15.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39ffd14646190c454f0d86e0d256b33f00a87a26ab410e619773b841d0e41416", size = 704313, upload-time = "2026-08-07T10:48:57.46Z" }, { url = "https://files.pythonhosted.org/packages/2d/53/b34fa9d0ff00f136f4d58ebb4c411ff634baed1eb412bb602a2bc8dcafcb/librt-0.15.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47318cd3a61401452de11282242937e3e057c4fd3dbaf601e269d0928a06c0a", size = 729847, upload-time = "2026-08-07T10:48:59.231Z" }, { url = "https://files.pythonhosted.org/packages/86/ac/fa4d7a424665040e95baf480a6d523446057684b6758624c85338e8a23b2/librt-0.15.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a56a1d4f859a82ca5b99fc4b82c9b027b15e3c455c5cd99e7d0719f27bb20b6c", size = 742736, upload-time = "2026-08-07T10:49:01.151Z" }, { url = "https://files.pythonhosted.org/packages/8a/f1/e17a9bb5de6fb8c3186ed1a7d68d21618b027ac2d3633e03d3b6109c67ae/librt-0.15.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:077471b3182db4e17c36ae91555f36a4d2c00080b267f749bcad34a478a9a302", size = 763454, upload-time = "2026-08-07T10:49:03.039Z" }, { url = "https://files.pythonhosted.org/packages/1d/ec/ecd02cd30935b931b9cdbfed6ab5a099c51b280b4e7baa274da80978ed27/librt-0.15.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:411ca4d1b905b860ceba7570dd6717a71dedaddcc4b0f77ece710aa41ee11f8d", size = 743296, upload-time = "2026-08-07T10:49:04.941Z" }, { url = "https://files.pythonhosted.org/packages/e6/b5/b3c2b8353ce820a4854f78d19321344242f89fa71c975b71132ba9bf242a/librt-0.15.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1256589e0b0adb31751d685a68bce29d73407ddf4ef05d4188f49d5dcf9566d9", size = 756217, upload-time = "2026-08-07T10:49:06.825Z" }, { url = "https://files.pythonhosted.org/packages/3c/52/6cc22542ba59146b05cca2a656f9ff8bb67e38e63d12c3b0cc183d837bf1/librt-0.15.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:f42b74a53e5f26a0ba0007411a7455b66c67ce4022a39cc1f56fc4efd65bcbab", size = 741934, upload-time = "2026-08-07T10:49:08.839Z" }, { url = "https://files.pythonhosted.org/packages/40/32/a04b72b1aa86e3be23b2ecff8c1aad2dcc955bd3956d6d26e7e34267e57a/librt-0.15.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:291bf73caf78b9e88d6fae9bfd693207ff7d832e2fdbe2cf8e746bc13f5f892b", size = 783763, upload-time = "2026-08-07T10:49:10.661Z" }, { url = "https://files.pythonhosted.org/packages/6c/f0/89eb11dffbe9279ff37144dec786927314502ae0b114f1449dc78c458aab/librt-0.15.0-cp315-cp315t-win32.whl", hash = "sha256:c16d15ee371643ab48dc8248a3e680ebbeca573a13af2c3dd0c985b142d77162", size = 104313, upload-time = "2026-08-07T10:49:12.305Z" }, { url = "https://files.pythonhosted.org/packages/6d/4a/1f1978c200f563beda63c36adff2d65bbecb81e365e8e69e572f5f70fbc6/librt-0.15.0-cp315-cp315t-win_amd64.whl", hash = "sha256:dbd605739f228912dc49027cb764456b9757750bdc2b6b7773164db7096c6fd1", size = 126889, upload-time = "2026-08-07T10:49:13.881Z" }, { url = "https://files.pythonhosted.org/packages/38/a6/800800bfed7b1fb10fc3f3d557785c3854e80d3f7a9800d784b176a1fc2d/librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc", size = 110700, upload-time = "2026-08-07T10:49:15.499Z" }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "mypy" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ast-serialize" }, { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/6a/878cc1097d4035f82bd516658d0c528d2a9955bc7b363afcbd0b07fea11b/mypy-2.3.1.tar.gz", hash = "sha256:47c1b1207258513a9d93495f69c8be9de73916186f0e52703e8c461b7a623419", size = 3992554, upload-time = "2026-08-15T03:03:38.549Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a4/be/c624d4241484f37dc62839e177ab607a9b8b3e96f0866544ca99e8e41d51/mypy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94f04929f1c44c35fb0061e912087edaf504acede963a4a7d00680bd089d8531", size = 13936739, upload-time = "2026-08-15T03:03:26.475Z" }, { url = "https://files.pythonhosted.org/packages/53/84/e3cf72f90dce5960871c82551c8fba6da05fc1018f79be41c047bd126bdd/mypy-2.3.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5d716048611e85ca9eefb2e1baa5d73ede389b5820ded260ea27c757d667af8", size = 14166460, upload-time = "2026-08-15T03:01:50.565Z" }, { url = "https://files.pythonhosted.org/packages/4a/ff/6b97d58aa0f79a5ab9b472db1f6d6df1b11a51d74d0c08ab3760d3a613ba/mypy-2.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b091a455111214cb5c9d54a57b9618e9a49f9fe2a42e4e1ac86e9d104ed96ce8", size = 15100476, upload-time = "2026-08-15T03:03:12.079Z" }, { url = "https://files.pythonhosted.org/packages/da/f0/cbb4b7d2ae3ac635f6b4f2d9b04070b8a92edf50da599d3b39e5ed109001/mypy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df12e20c9efd614738c71b390007ecd0181125afc4ccafca04d78a1d2eed2c01", size = 15347826, upload-time = "2026-08-15T03:03:02.856Z" }, { url = "https://files.pythonhosted.org/packages/5f/10/91dcdc6f8d43fc08e6a06ab1f9732f3abaaf835ac1b2e67b9dff56910855/mypy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:52eaf3a155f35cf80b40220288c861eb45f14a2340c1f6cbfbdb0feff32879d1", size = 11142615, upload-time = "2026-08-15T03:03:36.316Z" }, { url = "https://files.pythonhosted.org/packages/3d/8a/28d54535bf4b9aa43b2d8918c2ef660378b9f66b23d78dcee052744ae622/mypy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9b4eacbee8a69836c06eff6d0dd4e134a07c2b047755b30c08625fe214f322c6", size = 10141145, upload-time = "2026-08-15T03:03:07.406Z" }, { url = "https://files.pythonhosted.org/packages/85/da/d6effc4f808a842d91edc22535dc9e799d2ff6e91449168b7f47a0771f54/mypy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a32bbbb940af990d3be0b8af321c7b6815bb1b3b48142fe7459b9cc5f58959ff", size = 14047547, upload-time = "2026-08-15T03:02:57.707Z" }, { url = "https://files.pythonhosted.org/packages/e4/e6/478229701dab76f26485fc8ff5d6f241f393da22447400bbc56f6946aebe/mypy-2.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff715e45b2231a8e85de1d163d1b42791e4d7aab8f5145f85fee1b710b735aff", size = 14216515, upload-time = "2026-08-15T03:01:26.496Z" }, { url = "https://files.pythonhosted.org/packages/8d/fe/7c42327a3b21e84681f691982cbfe43f334a3685f3b683b72c376476c4fa/mypy-2.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:858fc57d3d91fa728e33e7ad71def60fc6272694607b306cd3292db53ae39080", size = 15307789, upload-time = "2026-08-15T03:03:31.62Z" }, { url = "https://files.pythonhosted.org/packages/59/f4/7e597edbe01b5a56fa958ce541302dcaabfed979966f1dffedbea0ea0fc2/mypy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:851833db876e7b650f93719c74b7879a08e338979c96054fdfc3bfd90a486355", size = 15548831, upload-time = "2026-08-15T03:03:15.55Z" }, { url = "https://files.pythonhosted.org/packages/a3/52/cb31e084bc0314a1e384bdd677a4b80e55af04ccac077545e2238b9d320a/mypy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:4c5095a327483591c94e0c8d3ef9e50d4ab1369b541eae007c1f23bc2a41f6bb", size = 11226359, upload-time = "2026-08-15T03:03:29.002Z" }, { url = "https://files.pythonhosted.org/packages/7a/47/88fcf6217b43fa2da81a8c2611370af18141536a4f0294bbf98b457d456d/mypy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfe022634a2a195406bd469e888d2eaf193b02ba7e607391cd7640374aaae3b", size = 10214707, upload-time = "2026-08-15T03:02:48.807Z" }, { url = "https://files.pythonhosted.org/packages/de/cf/862010ee800ca9c2bd0c4c0dacf0f092e5411824a09b8f97ad4be8fe250e/mypy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:114dff494000f18bd10d5d95d84b8567b26da60279ecbe838131841df20e635d", size = 13964542, upload-time = "2026-08-15T03:02:21.43Z" }, { url = "https://files.pythonhosted.org/packages/75/5a/3f3a2107b41e3e92e617e25daaee121413b91e9784bea733131ed4fecc5d/mypy-2.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8637731bb5eee3671eb2c3200827aa3564ed8a9309ecee4d1afe77e6d031bdb", size = 14168922, upload-time = "2026-08-15T03:03:00.351Z" }, { url = "https://files.pythonhosted.org/packages/8b/41/04dc4fe7e63d7820fa4eff272e95157d30cbea921388f3ab3fe77794cd0b/mypy-2.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c80fbc405ed8020f5ff3802dc18cf060197bcdd3fbdd6a26ef2fd34dfdd5226", size = 15244791, upload-time = "2026-08-15T03:02:31.089Z" }, { url = "https://files.pythonhosted.org/packages/96/fc/c3053b26b9054949285aa868cb6af8c10e7591541cacd79c5dcc06a1fcf9/mypy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84081f538ce27375045c02e3d7f81bd11d853400621ae245d87ce7b6c420ec74", size = 15501627, upload-time = "2026-08-15T03:03:34.128Z" }, { url = "https://files.pythonhosted.org/packages/70/4e/d77daab008bbc4e5001374d7928f4a260d28f0e6747af444fc4763f7a310/mypy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:e9144ac16fde007096f9563eb2041b4433c2d705c4218edeb79e7e9d01035ee6", size = 11243961, upload-time = "2026-08-15T03:02:11.952Z" }, { url = "https://files.pythonhosted.org/packages/f0/f8/7eb68c136e4abd30569fe31ef2bfcb7eceae9952cab80017c04cd09f5d0c/mypy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:77ad9529e67dca28e511f5cd5671436584ce91f6d3bac159a353158187b986ac", size = 10213219, upload-time = "2026-08-15T03:02:26.361Z" }, { url = "https://files.pythonhosted.org/packages/be/c4/42a49d44aeff804edf1b19acce0b49e8bd1a9c57dee9605dd8d980aa43d7/mypy-2.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:192abaedf75da1bc0b1cef104927e70ec49c1ef0031cc4825c7ee10a438ed24d", size = 13986778, upload-time = "2026-08-15T03:01:33.69Z" }, { url = "https://files.pythonhosted.org/packages/45/13/9331fd2dfed7194d66c5304072894a8be3e51e9deda6863c1eceaa35a43d/mypy-2.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf678dffd16efcda2c15cbd30e9ecc0081388e29ea23687a88e686ed92638dc3", size = 14188467, upload-time = "2026-08-15T03:02:40.554Z" }, { url = "https://files.pythonhosted.org/packages/78/f7/f4a34edab45667c5465855dc585a20e87978ffa8aee711445b7239d120c6/mypy-2.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e036f06b41630f4c8a1d48f9ac6aa26acc65f8be089973f5519da643318f03f", size = 15225538, upload-time = "2026-08-15T03:03:09.761Z" }, { url = "https://files.pythonhosted.org/packages/40/05/534b3590757bd05794f73e07f6666c2a77b8597ffed795c94ce570096aa0/mypy-2.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71af9c8a894e862b58e92abb08e53b05a384a1e5e5d6dc7cda59126211a53d82", size = 15480805, upload-time = "2026-08-15T03:01:41.134Z" }, { url = "https://files.pythonhosted.org/packages/55/da/bdfba852e2562f599624af5bb7d29e36b0b4f526f2b8bac85efe0dd1803d/mypy-2.3.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3c80cd23d85368bdd9f37d5231dfd97d35bcbf5bf41af96ef3a9b078ad1957f9", size = 7761712, upload-time = "2026-08-15T03:02:36.008Z" }, { url = "https://files.pythonhosted.org/packages/98/31/60fc64a74cdba4f2a5d642d32317993e479163e1ac7d91b695e5d15e2264/mypy-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:4956f34d145e145562a0a0bf367f642bbc85c04ec2baf47ae015947c3169a85d", size = 11423968, upload-time = "2026-08-15T03:02:06.931Z" }, { url = "https://files.pythonhosted.org/packages/a9/23/eb5950b24cd26ba3b78f87707a275568d633c77dae8e61c9661be6055ca6/mypy-2.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:cfb12e360242d23d91f5e978d94f58ea66acf5804c4fb6f2f794a20d4cb1b595", size = 10399323, upload-time = "2026-08-15T03:02:33.671Z" }, { url = "https://files.pythonhosted.org/packages/82/c7/f80f4e46c0b9a00eb5f78a79d49dda8bdf56a5230f7257fb33e76be04da7/mypy-2.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5f1c50bb05b64e2026b52867e8d21106f01313c744a2c4ecc34c90d12e8d6e2", size = 15121308, upload-time = "2026-08-15T03:01:46.053Z" }, { url = "https://files.pythonhosted.org/packages/5d/74/9b04f17c7074cc5188f02fb63a2ca1d43fedf479e84fe3091c39061a1d7f/mypy-2.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:667196b352f4cf304ded4c10f90cfc179263a1acfb3cdcfa984bdfd340d498bc", size = 15536590, upload-time = "2026-08-15T03:01:35.941Z" }, { url = "https://files.pythonhosted.org/packages/26/04/c837ef6208e567774e2ed1f863f8ba6ec4817b1b6dd426315e5d559b6ec9/mypy-2.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9c53e395c12cad2c6d4b67d5da7c6057638a132d85c08b73646b18f802a0045", size = 16791074, upload-time = "2026-08-15T03:01:31.073Z" }, { url = "https://files.pythonhosted.org/packages/37/68/48730230afa45192d5bd429a6a2ff24a6f8dedda90fdf2b221792b54518f/mypy-2.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:18162b128c3f9c703cd35f5537446900b0d21a2549aa7a95d21380d2ef643fb0", size = 17069183, upload-time = "2026-08-15T03:02:28.566Z" }, { url = "https://files.pythonhosted.org/packages/1c/ea/ca23fc9c20eeda09a15c9cbcf50015d0e73f409f6ead059e42aa69a608ff/mypy-2.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:30c0477d4aab7b7f39c8397dc877f2c96b9fe5588ec379f372c56eb63d599f63", size = 12154679, upload-time = "2026-08-15T03:02:04.809Z" }, { url = "https://files.pythonhosted.org/packages/3b/67/8d982126034990869466f73b8db80dcb2234a7ac39b4dad093e047a79835/mypy-2.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6941ab3619377bc3f32ca02876b07d27f216f5201604b664d3937ea0fdd23bb4", size = 10969159, upload-time = "2026-08-15T03:02:38.152Z" }, { url = "https://files.pythonhosted.org/packages/ee/f7/41e7f2d8117fbc7a7587286162ffe2f688984b69c46ed63cf5f2e4fc3bae/mypy-2.3.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6f041a6de52c9217ca125e78ba0a335cb7fd98a1c0580978e49ab2b126f70b57", size = 13990694, upload-time = "2026-08-15T03:03:21.919Z" }, { url = "https://files.pythonhosted.org/packages/06/85/8f665811a0c8f3bf6fa1d9acd665ec2d97a2bcc453ae68dcd92340941cd6/mypy-2.3.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5159ae60f5dbc3a498af5ba8365505808ac8031bc63f9e00304ad545d40bdd9b", size = 14203518, upload-time = "2026-08-15T03:01:48.455Z" }, { url = "https://files.pythonhosted.org/packages/2d/82/91b866c8546b120bff83b73a439d90d2d63ef3aff113599e6b8e4d566848/mypy-2.3.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a8a7a0a7f6f6e63995c0ac36fa0c07b127413fdc81f0439b7f3dccafd33561", size = 15220224, upload-time = "2026-08-15T03:01:23.577Z" }, { url = "https://files.pythonhosted.org/packages/c8/78/c226c99208ee40de7c768369fa533f933afa003dfdc606ff021450724e91/mypy-2.3.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2329c0501293d4e1f33bc15d04d6304d65a1cdda967ee93a05c1e681a3923133", size = 15501512, upload-time = "2026-08-15T03:02:09.453Z" }, { url = "https://files.pythonhosted.org/packages/a9/e7/7cfb3f106c393979f4cc37ad6c0586044d50401e3c35b0c003e4f3ba6bc9/mypy-2.3.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bb26deed807bdb0457cf3e3f1cd7c4a1cf9d66864eaf1b4a61e06805d4c6b1f9", size = 7761913, upload-time = "2026-08-15T03:01:55.65Z" }, { url = "https://files.pythonhosted.org/packages/99/3c/52affefa273b97939a1f474ae4a349c8718635c15b941112dfab4291b0c1/mypy-2.3.1-cp315-cp315-win_amd64.whl", hash = "sha256:375d7013876a8233b2d05be185bfa09f689696cd999ce8b1cfe6acac5c80e8a3", size = 11422533, upload-time = "2026-08-15T03:03:24.101Z" }, { url = "https://files.pythonhosted.org/packages/2a/b7/75643e70c72a5b346d8a9b1543c967ea8824df2ee3fb7ccba652c272b7bb/mypy-2.3.1-cp315-cp315-win_arm64.whl", hash = "sha256:586b3612214cceabb3c0f588c97e7d1e535393f06a60e912e994f6b3ace97523", size = 10397931, upload-time = "2026-08-15T03:02:55.265Z" }, { url = "https://files.pythonhosted.org/packages/10/ce/53be21f2d4adfcd26f63f1184a13ed797015ab463853f117e2e11e4d726f/mypy-2.3.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ef0c6335cda9d807f8193d8ff6204a72bc909fa9882aacbca14f43cdb7188306", size = 15118669, upload-time = "2026-08-15T03:02:51.479Z" }, { url = "https://files.pythonhosted.org/packages/62/43/20de757cd42989d291a17fad607742c4c74e875ce5cea00e5a5225020ac1/mypy-2.3.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e598c8c66401d26b150872154a286e6d484cf2789c3bb28a7556806298423021", size = 15545627, upload-time = "2026-08-15T03:03:05.132Z" }, { url = "https://files.pythonhosted.org/packages/7e/fc/092bdf77ad280eaf501422f0f3b966012b528076cc13e41a774861c907d1/mypy-2.3.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eda22fd4efa9dcd39331d1dede9b5b8b8a7fd69af07592e778433da98610d29e", size = 16764157, upload-time = "2026-08-15T03:02:23.958Z" }, { url = "https://files.pythonhosted.org/packages/94/5c/c94c4d62d909b07f552d0d9356d7acc943825558e602a64822ffa2231536/mypy-2.3.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a0ba2e57847849fb0d1fcdabb32786d223095ed8bc121dfe322bcdb3d9c46bc", size = 17073258, upload-time = "2026-08-15T03:02:14.573Z" }, { url = "https://files.pythonhosted.org/packages/c0/f7/511a88b89e478053c02d22039bb8f3ce4183efe8fd7a4f0a5910a8bb0a32/mypy-2.3.1-cp315-cp315t-win_amd64.whl", hash = "sha256:3f7e865dd51f235f60a2dbcd8728a1c095f5ca28f095d48a725b84cd935735c4", size = 12135505, upload-time = "2026-08-15T03:02:16.714Z" }, { url = "https://files.pythonhosted.org/packages/71/bf/02573b56964ecb0f7c644f915f53c325ae15c3faec521c5adf11599a32df/mypy-2.3.1-cp315-cp315t-win_arm64.whl", hash = "sha256:8ad80807dc3ab8ea978b1b2b6e4a657194ace1d4ef03e0e731aff1abd517da29", size = 10962647, upload-time = "2026-08-15T03:01:43.712Z" }, { url = "https://files.pythonhosted.org/packages/8e/41/9675c7a1e78edecfba0b79e587a52594c56e189368261dc7b3a7fffb9527/mypy-2.3.1-py3-none-any.whl", hash = "sha256:6ed5c7e3419083268e5c9258bd1c1ef91af44a9e89374dbcaf37b775716e72eb", size = 2754338, upload-time = "2026-08-15T03:02:53.4Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "nodeenv" version = "1.10.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] name = "packaging" version = "26.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] name = "pallets-sphinx-themes" version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-notfound-page" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/60/1ee4b7dfce36150cea485b6d47358d58e59e96e8c5bdadee8030d483ce38/pallets_sphinx_themes-2.5.0.tar.gz", hash = "sha256:ef8609704b80e8a5d508a88adeb0559234311a1cf3362427aca9d283b22c80ef", size = 216582, upload-time = "2026-02-25T16:28:22.83Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bc/b4/313608ebc395ff81ef2805790cddf626867caff7c40cb3204658830623e3/pallets_sphinx_themes-2.5.0-py3-none-any.whl", hash = "sha256:5a41191d4d5dd9ac9cd7a17b14ebdea700626d8a7a708ff66b1c6e1e4ee8b4da", size = 144591, upload-time = "2026-02-25T16:28:21.413Z" }, ] [[package]] name = "pathspec" version = "1.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] [[package]] name = "platformdirs" version = "4.11.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b8/d7/e7bfbc86e9f99ff7807e24de7703f032e9c9ba80bb355cf26e0e9bc5a75e/platformdirs-4.11.3.tar.gz", hash = "sha256:66a73d38a849810252df809a3d8bcbda8e26f6c189920e7535ad608a48dbb5ab", size = 33050, upload-time = "2026-08-13T22:43:27.52Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/19/a9/c34aebedd3a4c9afe5101b1b8713710b3fec18087c8a36c35d2f909861bd/platformdirs-4.11.3-py3-none-any.whl", hash = "sha256:5ed065d443751de711da036041a7a214122efc4a4de393b3f4137ba5576540e7", size = 23491, upload-time = "2026-08-13T22:43:26.121Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pre-commit" version = "4.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, { name = "identify" }, { name = "nodeenv" }, { name = "pyyaml" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, ] [[package]] name = "pre-commit-uv" version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pre-commit" }, { name = "uv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8f/4f/c60d3549065b1d6d9242e81d37910a824bfb2a1df12e0a79c928d81885c4/pre_commit_uv-4.3.0.tar.gz", hash = "sha256:7a46537c0c2cd346dc6f3b6d64c7c45250189d5e3d5a17f626ec3e3b2503bf46", size = 7473, upload-time = "2026-07-21T13:09:43.33Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/8b/86164aa971db50b752948756417b2e039f42721114afa2a9972543b7186b/pre_commit_uv-4.3.0-py3-none-any.whl", hash = "sha256:04cd06f2402e21ab48f2560ebea6eea03fbc46d428af8ceb862060b047f91f42", size = 6210, upload-time = "2026-07-21T13:09:42.382Z" }, ] [[package]] name = "psutil" version = "7.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pygments" version = "2.21.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] [[package]] name = "pylibmc" version = "1.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ad/eb/8767d00eef0d4012e63a1b76025da5dbf307888246b2b50476841416c12b/pylibmc-1.6.3.tar.gz", hash = "sha256:eefa46115537abad65fbe2e032acd1b3463d9bf9e335af4b0916df4e4d3206e0", size = 63224, upload-time = "2022-08-30T07:33:38.884Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/55/c6c66d6c54e185463514a2d45c60115ee39193a6466bcd89d6bdc543e68b/pylibmc-1.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c35816082848723455071670770d89b5a531d40e9063fe4e942ea456f86da49", size = 206686, upload-time = "2022-08-30T07:33:08.877Z" }, { url = "https://files.pythonhosted.org/packages/d1/74/91958c0cfbab6dd40a770ef1fa283f63fb3efc78f3d69a0f2ebd9a343ab6/pylibmc-1.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9d2ff6d702eb5ae502e29d97772dc85c749d596c6cb8c82a5d18f175fd4eabcc", size = 205913, upload-time = "2022-08-30T07:33:10.16Z" }, { url = "https://files.pythonhosted.org/packages/03/81/12de24d610512264318f180209a63131b9b9e7eb033b93baf7b9c613f51b/pylibmc-1.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e589b7e70dec4daf0da1216789713c753d85611d70cfcd32574161cc75b1527e", size = 1177563, upload-time = "2022-08-30T07:33:12.469Z" }, { url = "https://files.pythonhosted.org/packages/35/00/dc4d8c01b13a81e25f779271ed0d88f800f111d71ac3b010f6f8b4dc1b50/pylibmc-1.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b93e381dec1520a3fec922765e04679ac553d2f3fda830a5faa7cdc527280a2", size = 1226742, upload-time = "2022-08-30T07:33:14.123Z" }, ] [[package]] name = "pyproject-api" version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/bd/2f985c12bf33fdd8637e3a8f9418d6806f177601dee7c4924d0b2bb28650/pyproject_api-1.11.0.tar.gz", hash = "sha256:b8807d85a293e6c9f133e6575946fed45f1d42b22d58c780b33aa2421a799549", size = 23787, upload-time = "2026-07-21T13:09:33.744Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/6a/2822ee12bafe87af8f6cc620f7d1f126e77f82ce56ba888b94a9af5a979c/pyproject_api-1.11.0-py3-none-any.whl", hash = "sha256:860060c8832dce983b5eec6f41c4c43eb3ec06ff7332387a63acdf5ca27b68d8", size = 13275, upload-time = "2026-07-21T13:09:32.559Z" }, ] [[package]] name = "pyright" version = "1.1.411" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/ab/265f7dc69d28113ebba19092e57b075f41543b2ed048429c5f56e2b88eac/pyright-1.1.411.tar.gz", hash = "sha256:d885a0551f2e763b089a02702174e7f4ba77548cddabc972ab86d1f7f1b0f998", size = 4112861, upload-time = "2026-06-25T02:14:06.37Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" }, ] [[package]] name = "pytest" version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] name = "pytest-asyncio" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] [[package]] name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] name = "pytest-xprocess" version = "1.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "psutil" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/6f/e53d349445b4280f6b47bbed87127e994f331c335377d5e72407b376ad46/pytest-xprocess-1.0.2.tar.gz", hash = "sha256:15e270637586eabc56755ee5fcc81c48bdb46ba7ef7c0d5b1b64302d080cc60f", size = 13232, upload-time = "2024-05-19T16:12:21.819Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/63/cf/91b94238c843bfbae0a6674df45015aa3908b91454c496b2eb29e7991255/pytest_xprocess-1.0.2-py3-none-any.whl", hash = "sha256:0b0444d1f789fd9b4ba8b6b38b1d0139f226ab14091db2698a0521c1770523dd", size = 9628, upload-time = "2024-05-19T16:12:19.773Z" }, ] [[package]] name = "python-discovery" version = "1.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/b7/ac44da2cf0e53ada0e419033c2d058219c95dc1403126f163304c9e814b1/python_discovery-1.5.2.tar.gz", hash = "sha256:45fd4f20a4e3f9b7bf2e0817870bc8e3b320a19658da177af800768c82dbf354", size = 82350, upload-time = "2026-08-12T14:05:26.419Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/45/689603d04b3bb8d7faa00f25c24acef993aab7813b3dbbfc472a459ab0b5/python_discovery-1.5.2-py3-none-any.whl", hash = "sha256:3e338c2d0f15dfaeea57493f4c2c6caebe0e998ea815c30ae8bf8ee21f1112d3", size = 38350, upload-time = "2026-08-12T14:05:25.113Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] name = "redis" version = "8.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/99/604f0b666d4c616d891cf77ebb9db6bb21601344c051aebf1b72b9ff915f/redis-8.1.0.tar.gz", hash = "sha256:6e1a19beef9225c83efd689c7e6b7da2d5215b1f42cd13b7fc3714d0a09c7b25", size = 5254356, upload-time = "2026-07-30T08:51:00.269Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/66/9d/c5731f6e3608663d4d3656fd8d3aecee8b509c3082818f5a13eae925baea/redis-8.1.0-py3-none-any.whl", hash = "sha256:a4fe1aac3d3b3cc791d4b3d5931c5a956045dc951ee74d1c913ee3ac4d2ee9fb", size = 560618, upload-time = "2026-07-30T08:50:58.497Z" }, ] [[package]] name = "requests" version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] name = "roman-numerals" version = "4.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] name = "ruff" version = "0.16.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" }, { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" }, { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" }, { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" }, { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" }, { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" }, { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" }, { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" }, { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" }, { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" }, { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" }, { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" }, { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" }, { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" }, { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" }, { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" }, { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, ] [[package]] name = "snowballstemmer" version = "3.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, ] [[package]] name = "sphinx" version = "9.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ { name = "alabaster" }, { name = "babel" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "docutils" }, { name = "imagesize" }, { name = "jinja2" }, { name = "packaging" }, { name = "pygments" }, { name = "requests" }, { name = "roman-numerals" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, { name = "sphinxcontrib-htmlhelp" }, { name = "sphinxcontrib-jsmath" }, { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, ] [[package]] name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15'", "python_full_version >= '3.12' and python_full_version < '3.15'", ] dependencies = [ { name = "alabaster" }, { name = "babel" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "docutils" }, { name = "imagesize" }, { name = "jinja2" }, { name = "packaging" }, { name = "pygments" }, { name = "requests" }, { name = "roman-numerals" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, { name = "sphinxcontrib-htmlhelp" }, { name = "sphinxcontrib-jsmath" }, { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, ] [[package]] name = "sphinx-autobuild" version = "2025.8.25" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "starlette" }, { name = "uvicorn" }, { name = "watchfiles" }, { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, ] [[package]] name = "sphinx-intl" version = "2.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, { name = "click" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/21/eb12016ecb0b52861762b0d227dff75622988f238776a5ee4c75bade507e/sphinx_intl-2.3.2.tar.gz", hash = "sha256:04b0d8ea04d111a7ba278b17b7b3fe9625c58b6f8ffb78bb8a1dd1288d88c1c7", size = 27921, upload-time = "2025-08-02T04:53:01.891Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3b/156032fa29a87e9eba9182b8e893a7e88c1d98907a078a371d69be432e52/sphinx_intl-2.3.2-py3-none-any.whl", hash = "sha256:f0082f9383066bab8406129a2ed531d21c38706d08467bf5ca3714e8914bb2be", size = 12899, upload-time = "2025-08-02T04:53:00.353Z" }, ] [[package]] name = "sphinx-issues" version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/4e/c9a46b19c90d4d3cdcbcaf0e4d392b09e5b41ec75b134bc5a1aa192be94f/sphinx_issues-6.0.0.tar.gz", hash = "sha256:f40f2c71cecb2068c7f06a53825778b674d58d8395b4ea60551a36164d2988e3", size = 15230, upload-time = "2026-03-13T17:23:32.501Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/29/8a/28333d222ac31539aa0d818db0790077e5a6a408f933b4bf64f7d3440ffc/sphinx_issues-6.0.0-py3-none-any.whl", hash = "sha256:c7ed2915059526d02022733985a7712d4b2b64a707e16b98294ed9758e64df4f", size = 8362, upload-time = "2026-03-13T17:23:31.031Z" }, ] [[package]] name = "sphinx-notfound-page" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6a/b2/67603444a8ee97b4a8ea71b0a9d6bab1727ed65e362c87e02f818ee57b8a/sphinx_notfound_page-1.1.0.tar.gz", hash = "sha256:913e1754370bb3db201d9300d458a8b8b5fb22e9246a816643a819a9ea2b8067", size = 7392, upload-time = "2025-01-28T18:45:02.871Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cd/d4/019fe439c840a7966012bbb95ccbdd81c5c10271749706793b43beb05145/sphinx_notfound_page-1.1.0-py3-none-any.whl", hash = "sha256:835dc76ff7914577a1f58d80a2c8418fb6138c0932c8da8adce4d9096fbcd389", size = 8167, upload-time = "2025-01-28T18:45:00.465Z" }, ] [[package]] name = "sphinx-tabs" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "pygments" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/30/ca5b0de830f369968d8e3483dd45a8908fd10169c05cd9837f0bd075982e/sphinx_tabs-3.5.0.tar.gz", hash = "sha256:91dba1187e4c35fd37380a56ac228bbd54c6c649b2351829f3bf033718277537", size = 17006, upload-time = "2026-03-03T23:00:30.404Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/45/6adc5efeb19fd5fed4027e520b5c668ce58236a2b271ade5533c4c116276/sphinx_tabs-3.5.0-py3-none-any.whl", hash = "sha256:154be49de4d5c8249ea08c5d9bf88ca8f9c31e00a178305a93cbc33e000339e5", size = 9871, upload-time = "2026-03-03T23:00:28.89Z" }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] [[package]] name = "sphinxcontrib-log-cabinet" version = "1.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/26/0687391e10c605a4d0c7ebe118c57c51ecc687128bcdae5803d9b96def81/sphinxcontrib-log-cabinet-1.0.1.tar.gz", hash = "sha256:103b2e62df4e57abb943bea05ee9c2beb7da922222c8b77314ffd6ab9901c558", size = 4072, upload-time = "2019-07-05T23:22:34.596Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/e7/dbfc155c1b4c429a9a8149032a56bfb7bab4efabc656abb24ab4619c715d/sphinxcontrib_log_cabinet-1.0.1-py2.py3-none-any.whl", hash = "sha256:3decc888e8e453d1912cd95d50efb0794a4670a214efa65e71a7de277dcfe2cd", size = 4887, upload-time = "2019-07-05T23:22:32.969Z" }, ] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] [[package]] name = "starlette" version = "1.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" }, ] [[package]] name = "tomli" version = "2.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] name = "tomli-w" version = "1.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] [[package]] name = "tox" version = "4.60.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, { name = "colorama" }, { name = "filelock" }, { name = "packaging" }, { name = "platformdirs" }, { name = "pluggy" }, { name = "pyproject-api" }, { name = "python-discovery" }, { name = "tomli-w" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/1f/3acba68301b0081cec87b1769ba4c3a4a0a30f20879a05420673c7e4671d/tox-4.60.0.tar.gz", hash = "sha256:6f93bb580d35e00fc69cfec1116eecbe21155f747753ca5019374f7c69b2eac9", size = 301598, upload-time = "2026-08-13T23:14:26.944Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fa/e6/3a1042add765c0e1e67f76a3b0f2036bfe6b0229ace83c6ff9638a5e391b/tox-4.60.0-py3-none-any.whl", hash = "sha256:175abbc4cdef615d66874c0843be4f44c353c14aab6d89939bb22246f84122bd", size = 226332, upload-time = "2026-08-13T23:14:25.324Z" }, ] [[package]] name = "tox-uv" version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tox-uv-bare" }, { name = "uv" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f9/d7/3fce976b9e295218a2d7e541e0f1e9b1259cb893de7a8d277952a66a798d/tox_uv-1.36.0-py3-none-any.whl", hash = "sha256:5f81b39be3fe4e14c6b9bb7ba637ed97d34efa6214efd5f525d95ee9559a99ac", size = 6564, upload-time = "2026-07-21T13:09:54.316Z" }, ] [[package]] name = "tox-uv-bare" version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "tox" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/df/9f90a59de8c87cece6e691eee53dc1cb0a824d73aea8f380ae2f6ecb71de/tox_uv_bare-1.36.0.tar.gz", hash = "sha256:d9b0a2fd0f74fa65d9597108f8a0ef7abb08651c9196a998a6073b781b45cfd0", size = 32548, upload-time = "2026-07-21T13:09:56.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/0a/6dc462e4fb543305283a6157c80f43e3d12ca4702da6ae6521d541c6b55c/tox_uv_bare-1.36.0-py3-none-any.whl", hash = "sha256:ba397dd0396df95a75744d4e42a50ee27207c0ffcf277b62ffba9c3de455a939", size = 22489, upload-time = "2026-07-21T13:09:55.389Z" }, ] [[package]] name = "types-cffi" version = "2.0.0.20260518" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/0b/b352742758a6054d1053783887bf8cfb739deda1102fda8722294bdc01f7/types_cffi-2.0.0.20260518.tar.gz", hash = "sha256:f9707e66c13454789a58f8843d1ded4a66f1e9c8b10bd24d5eb5e0f25c0c5472", size = 17790, upload-time = "2026-05-18T06:06:50.672Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/44/d3b4aafa20a3f76384ba19a513d39272add13746dcfe0409d8d4974fd464/types_cffi-2.0.0.20260518-py3-none-any.whl", hash = "sha256:5b68a215a95d0eac4203b58e766ff7fe40c2e091b1fa1a9e54111f04cc560084", size = 20198, upload-time = "2026-05-18T06:06:49.83Z" }, ] [[package]] name = "types-pyopenssl" version = "24.1.0.20240722" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "types-cffi" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/29/47a346550fd2020dac9a7a6d033ea03fccb92fa47c726056618cc889745e/types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39", size = 8458, upload-time = "2024-07-22T02:32:22.558Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/98/05/c868a850b6fbb79c26f5f299b768ee0adc1f9816d3461dcf4287916f655b/types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54", size = 7499, upload-time = "2024-07-22T02:32:21.232Z" }, ] [[package]] name = "types-redis" version = "4.6.0.20241004" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "types-pyopenssl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/95/c054d3ac940e8bac4ca216470c80c26688a0e79e09f520a942bb27da3386/types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e", size = 49679, upload-time = "2024-10-04T02:43:59.224Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/55/82/7d25dce10aad92d2226b269bce2f85cfd843b4477cd50245d7d40ecf8f89/types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed", size = 58737, upload-time = "2024-10-04T02:43:57.968Z" }, ] [[package]] name = "types-setuptools" version = "84.0.0.20260812" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4f/cd/3b2a3362a526f91c33f785a291462b2ec448ae531101c62372fc30a21f53/types_setuptools-84.0.0.20260812.tar.gz", hash = "sha256:09bedc248ebbb7a232c9419dfcdca329706e61bf2aa5743e9424d027f1d956b4", size = 46545, upload-time = "2026-08-12T03:52:42.316Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8a/e5/3a41cab4066465593facd660b2683c017f3d88d3297cdd26db0677b8e479/types_setuptools-84.0.0.20260812-py3-none-any.whl", hash = "sha256:799c08e4bc6a288e8a0b538afb5f5ff320a08927ba8dbedb30c74ee3ba5d867b", size = 70320, upload-time = "2026-08-12T03:52:41.154Z" }, ] [[package]] name = "types-uwsgi" version = "2.0.0.20260518" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b3/f9/2bdee60539cbe3d53abe05ce92a10fa0d25ec4eaba2bae2f8f698f7ba031/types_uwsgi-2.0.0.20260518.tar.gz", hash = "sha256:93f68c5d52a20d67fd770e8d14782b98cdfc1920a4af9dc29ac39b5c49946094", size = 11442, upload-time = "2026-05-18T06:04:42.601Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/92/5e/8eae5b16a7233079fd762f05630a51de63c3d9f487fa14bfc4329081b803/types_uwsgi-2.0.0.20260518-py3-none-any.whl", hash = "sha256:35d39e94d00e343057e80940af99763c5ce5d30b9489b6a1809762dbcf07695a", size = 12168, upload-time = "2026-05-18T06:04:41.837Z" }, ] [[package]] name = "typing-extensions" version = "4.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] name = "urllib3" version = "2.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] name = "uv" version = "0.12.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7c/b0/3085b844fe59aa319a3f94a5cca9938fffecc82705aa9c2762a749f7095c/uv-0.12.5.tar.gz", hash = "sha256:442a21d181faae21742aaaf6d2091a0d27755d3eac344061a9a00c90169b7524", size = 7101936, upload-time = "2026-08-14T19:56:57.693Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b8/4c/6412d4a618230db699118b362ec41c54795f93992b43c53e225bd0213501/uv-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:2bd62134e56af35b9cf017aaf8ae41a605d6501dd49afc35b70b544a45dd8354", size = 23310055, upload-time = "2026-08-14T19:55:51.357Z" }, { url = "https://files.pythonhosted.org/packages/bd/ec/d76387b388fa21620088b89b9c67f2596a707add585104e0cb5e8abf55f2/uv-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1a06c8bc4d43b5f6c1e3f2ae3d0f6455b07515f762516f95e52e6c0cbccedf15", size = 21401335, upload-time = "2026-08-14T19:55:55.371Z" }, { url = "https://files.pythonhosted.org/packages/6d/bc/81ab953b7261ae6be40874b1f283a10873871e02eb353d354614dd8da96b/uv-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d87156bc174d94fae890bb7a261e2867140abb9fe1e9de81a5295e582fb9d0f5", size = 19290641, upload-time = "2026-08-14T19:55:58.998Z" }, { url = "https://files.pythonhosted.org/packages/7d/13/07585043c10e648820bf826474dac46864ce6691da5dc52fee43c5c7523a/uv-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2d65b7b3bc3fd28678f62aa7fb5d90f106ad9782c1354af60b6cecdf9ea9ecd9", size = 22245569, upload-time = "2026-08-14T19:56:02.729Z" }, { url = "https://files.pythonhosted.org/packages/3e/6d/310f8f56f8d001b4000112a09d7b7de80fb2024a90208fabb9ddc457c123/uv-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:712624b62e25c84e5a10fc6aa144d8a81b685fdc067a54a7ca4367d75d2cf791", size = 22745152, upload-time = "2026-08-14T19:56:06.426Z" }, { url = "https://files.pythonhosted.org/packages/92/da/7922b67eec5ee03e94333c5841b682c335033ee80acac17c3417bd752656/uv-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9656ac7a00fd4314980fb0f790df1c1f3fa9cbcf9af9c6f611b19448b9da687", size = 22787947, upload-time = "2026-08-14T19:56:10.149Z" }, { url = "https://files.pythonhosted.org/packages/62/55/5dbaed832a4b36809ef8a07c8e56e9fee0dedb0aa0454f6d232b6e468f2c/uv-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:568485b44e848eb3693f85d6b00299ccd8fc4d26902030dbf24f549c276db9ca", size = 23367616, upload-time = "2026-08-14T19:56:13.768Z" }, { url = "https://files.pythonhosted.org/packages/11/77/baf761d12bb66efb01706e3bbb5926ed0d13cb0a40539a661fcfffd46de4/uv-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd08c82831b0033330f8eeeb0d90f938a4d999f25569bee68a975c736142d795", size = 24586263, upload-time = "2026-08-14T19:56:17.57Z" }, { url = "https://files.pythonhosted.org/packages/c3/a8/76c1031c4834c959bb8a8059c9feabeaa77488ce8b6a3529d6d929ae81cf/uv-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edd9ff6154b891146a342c143cd29b330ad97ac6a4b20ff4a99a20a4da84ceca", size = 24160655, upload-time = "2026-08-14T19:56:21.568Z" }, { url = "https://files.pythonhosted.org/packages/93/22/dacc9a0bc8604187a1ba954a3aef8329e4104eb0af772d2c3c634893bd9b/uv-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e195ccf1ed60c8bb24a6447ce306441a4181d54b602407e09bc56e963911c15", size = 23657089, upload-time = "2026-08-14T19:56:25.144Z" }, { url = "https://files.pythonhosted.org/packages/39/98/e8f9c071622f2cb4072d8b587d27b27d23cf0d3ebf8b3687f5af6030f587/uv-0.12.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:58abfb0f658b39a834307a11223bc170294ea214263b4c99ecc7663720d43544", size = 22379954, upload-time = "2026-08-14T19:56:28.789Z" }, { url = "https://files.pythonhosted.org/packages/73/95/4c3f060e95f7cbe9177b4ab361f0cbfc4ae22e5a49b22e73eee9f0d0a6ca/uv-0.12.5-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:6ad2c455f1fe4d2962f6fd7ccb3b1f61c61856681c9d99f40e170b2074353fa3", size = 23318163, upload-time = "2026-08-14T19:56:32.504Z" }, { url = "https://files.pythonhosted.org/packages/a0/96/ca0497ef8912ef48dbbc9982a8b4212260c34d56bfd0d45fe67b31942121/uv-0.12.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a05b497c2a948c8600f4c831a89852b4d2514b7f561074225cc9edd0cc4811e2", size = 23470437, upload-time = "2026-08-14T19:56:36.525Z" }, { url = "https://files.pythonhosted.org/packages/60/e7/8bdc37669a6cd2b46a2ec08ccbb58c61395ec84a073e199f5a4a64bb998f/uv-0.12.5-py3-none-musllinux_1_1_i686.whl", hash = "sha256:7817f8e957960f9ddc452ea353f283c0d6393e2e31b400276485adced5b1f371", size = 22545803, upload-time = "2026-08-14T19:56:40.606Z" }, { url = "https://files.pythonhosted.org/packages/37/cc/01e39e1dbeb838a6b3c26bf97c867d6f366459b22a38bea691af8c6c94c0/uv-0.12.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:dc14e4f81a99b585a891350c60d1ff4557d54cb3c3c81fa45fd4e0dd512ba752", size = 23874113, upload-time = "2026-08-14T19:56:44.193Z" }, { url = "https://files.pythonhosted.org/packages/0a/38/9053599a73a351d1cd34195c7a48c1db4d4d51b57b543607fad7ecf9354c/uv-0.12.5-py3-none-win32.whl", hash = "sha256:39bb102766c95571781a7b4c611675ea213e08df5c680f3936279b3c0d1f6c3c", size = 20744641, upload-time = "2026-08-14T19:56:47.689Z" }, { url = "https://files.pythonhosted.org/packages/ce/f6/a9af9311c7f5640ca2bfcfdedb7aca37fa6d1d9f5c981fb50c5be02b7477/uv-0.12.5-py3-none-win_amd64.whl", hash = "sha256:455c3e57602e2141e66e2f0bf685898c9c5e5a70377d14c9a71554a3baf3ddbf", size = 21621812, upload-time = "2026-08-14T19:56:51.126Z" }, { url = "https://files.pythonhosted.org/packages/bc/fb/e1266399f755f97a0783de379f2fed6dae0a2a240db32fe5a2eb976fec8a/uv-0.12.5-py3-none-win_arm64.whl", hash = "sha256:bea86f27a027e0e3af908db4bdd4f1ceef3ca2bd47673b5ccca7f550e325b1b4", size = 20381876, upload-time = "2026-08-14T19:56:54.883Z" }, ] [[package]] name = "uvicorn" version = "0.52.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" }, ] [[package]] name = "virtualenv" version = "21.7.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, { name = "python-discovery" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2d/dc/a6eb1ddfa7f1e390fa599b078453c97edb3f6f846b34fb4eac3e8ea16401/virtualenv-21.7.4.tar.gz", hash = "sha256:c9d960c95fa458171e58222a5ccab7465298e4b6559977865e627c4719f1e825", size = 5345511, upload-time = "2026-08-10T22:54:33.316Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/40/4c/eb2f52aeeaf30dbd073d315a251a63ae2b8263171ec4428c135140cb0802/virtualenv-21.7.4-py3-none-any.whl", hash = "sha256:376ec93cd6aab3044fa395d7db226db38043b7b5748948044b2a87168525e843", size = 5324444, upload-time = "2026-08-10T22:54:31.515Z" }, ] [[package]] name = "watchfiles" version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, ] [[package]] name = "websockets" version = "17.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f7/96/e01084f83a64bcb3a27994bd0cb0db68ff29d9c6707fae37ec19b18ba990/websockets-17.0.1.tar.gz", hash = "sha256:5baa9bc0dfbae8c507e51c8cf1b6d4628086f7a87bbd3a9952bd5f035451f1cc", size = 183298, upload-time = "2026-07-31T11:31:27.665Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/54/b2bce5b754b91b727b852e78af6d7193d4fe985e420dc54e6c2abe161c1c/websockets-17.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c38515cb54902f7e97d0239e81ef46c4444f9475f4807fb9bbdb789b4089abcf", size = 212573, upload-time = "2026-07-31T11:29:04.803Z" }, { url = "https://files.pythonhosted.org/packages/78/1b/eaefeb695b217d8c735fc377ab53c6b00bc9ed64a01a3f6f797096ac0ee8/websockets-17.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70d438268e49f1a4bd096b6b6f7010f3ab48b5db2574dbf7d8c864c46ce7a06a", size = 210262, upload-time = "2026-07-31T11:29:06.298Z" }, { url = "https://files.pythonhosted.org/packages/87/2d/68439a174c74969fb51619bbe9af9496826610883b828a2edd2f022c94fc/websockets-17.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b52c76b8a870b141b7ca0705289452183ce7a523101954ccfe29a25986a673f", size = 210535, upload-time = "2026-07-31T11:29:07.553Z" }, { url = "https://files.pythonhosted.org/packages/a4/ab/13a856b488dbac7fd3c5473e38098897cda0baa04e3ca3b34ec6c8a32b46/websockets-17.0.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b98860aefbd3d9bc8e3c7f0eefb83b11142b16110739c68cd33d3b4d6e84e536", size = 219602, upload-time = "2026-07-31T11:29:09.227Z" }, { url = "https://files.pythonhosted.org/packages/45/a1/2b100e71aa1fe283ec8fb73f8b74a8576d855486a49117903b100dd3b78d/websockets-17.0.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d41e9845514754a42d1d83b2fca9d27fee2ca7b3b0bee6843ba5a9bb2b6e25ac", size = 219873, upload-time = "2026-07-31T11:29:10.362Z" }, { url = "https://files.pythonhosted.org/packages/3b/71/92e6146d3588d145136c0e0e16d106bbb855bb5047e13ec3fdee39cce770/websockets-17.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9aac6081513f02eac3f8caace800dbfc5c608b69e4a7bef69e414eabfc95aa1", size = 221108, upload-time = "2026-07-31T11:29:11.708Z" }, { url = "https://files.pythonhosted.org/packages/09/8d/357afa2ffd29686536109e7e3cb2a94f2d09e535df4cf3989393dc506a40/websockets-17.0.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b85b960a4507b0714c0a1246d031be9118d908ee974dc085257297a955205f1d", size = 224401, upload-time = "2026-07-31T11:29:12.959Z" }, { url = "https://files.pythonhosted.org/packages/01/27/9efba1e7a8df48e405d017e67513c6b2a8f0b59c8500b820c47cd5f3dea9/websockets-17.0.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c356dbddab0a529ed7574f78f559d75a223735c321c28f6f587fbf02b11ed301", size = 221670, upload-time = "2026-07-31T11:29:14.199Z" }, { url = "https://files.pythonhosted.org/packages/01/85/ab27d62103e8a150f3657e043e5fc711ad3e018c8cd8a715093e93d7640c/websockets-17.0.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5661f868ef191d33dfc6a0cc7c5b3d495f0cc8bb3f8b30d87bda8755c61c95f5", size = 220442, upload-time = "2026-07-31T11:29:15.417Z" }, { url = "https://files.pythonhosted.org/packages/6d/04/289e00b8001b622b0c397a6901fcc4aa8f34a6d2ed42be17f2704f2faae1/websockets-17.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2fa2cb465a131c347ba6717a78c887746e73edb1c131d01c982d6ef0d68b82e0", size = 217760, upload-time = "2026-07-31T11:29:16.772Z" }, { url = "https://files.pythonhosted.org/packages/2f/70/0fe58cdac988dfc0066786cc07b09dfd72b48b0c01e5de667721192b2e6e/websockets-17.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55383d8177b3c99fd873ee5db0e0193f4c1dd4a3feaccf1a4a03c1b7cf539cac", size = 220597, upload-time = "2026-07-31T11:29:18.075Z" }, { url = "https://files.pythonhosted.org/packages/d5/76/d3eaf120710d1a791d1c7a4963f0b50a589df8ba675394fd2a97dfea3746/websockets-17.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:038cfad5d5417f8bb09295abe986029a26d22f34bda622ccc79b670efd4dab56", size = 219187, upload-time = "2026-07-31T11:29:19.468Z" }, { url = "https://files.pythonhosted.org/packages/e6/00/4e9ae886bdb1647537176ca33fca66d79dddb3773d213ac98ddc6bba9ab4/websockets-17.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:15920057a6b723f84734f0641403bca163a4b176e5af809ee4f0c4a1e75e9fed", size = 219955, upload-time = "2026-07-31T11:29:20.641Z" }, { url = "https://files.pythonhosted.org/packages/7c/67/cd7cc6849a86cf8c9979c0c570b6b6ccee75d2872854238d8d54e77be6ec/websockets-17.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5508f38c98ac29def9e747b87543b008a58b075df6da70b2cf2e0b47073d33bb", size = 221002, upload-time = "2026-07-31T11:29:21.753Z" }, { url = "https://files.pythonhosted.org/packages/de/5c/4d14eaf7b2f1448d1af24c1641f04eb74c1632a5802952aac4b7e068b8e7/websockets-17.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a68e604c6d1b0338e46652e2688cbce8096ad9c03548b075fda9e2ea19a9b7dd", size = 218579, upload-time = "2026-07-31T11:29:22.888Z" }, { url = "https://files.pythonhosted.org/packages/15/67/ff8bc4b8a6ec235ed8985de12fecc59ad2cd68cc8fc79b97deaa42e412ac/websockets-17.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8af570fc29cd998a921c7131c8ac81d9434466d6d25300cb12a690fb56a8a08", size = 219612, upload-time = "2026-07-31T11:29:24.052Z" }, { url = "https://files.pythonhosted.org/packages/91/eb/103d81d655bab3ffd5c7d5d4b08f92c374499decd1d4be6035ce715b385b/websockets-17.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:246927ae9ae06ca0d42a483a4bdb80d4862e1ee5b4cab37c354a5e1ad8356448", size = 219846, upload-time = "2026-07-31T11:29:25.421Z" }, { url = "https://files.pythonhosted.org/packages/b8/1e/3495161b1827941258545604fdf72e3e053d03f25bb61752228a784c26a1/websockets-17.0.1-cp311-cp311-win32.whl", hash = "sha256:1d4cf7e8e5b8b1fa40758ac7524843a00237b124ab217e227542cafcfeb7a946", size = 213046, upload-time = "2026-07-31T11:29:27.094Z" }, { url = "https://files.pythonhosted.org/packages/7b/b1/ba0ce59681db38c320a6d485f95a497ddea20356d9a9e8e70615ddd867b9/websockets-17.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:02f0b037a737d0cb0c33866c97bcd1a0b73170dfbf42d69d8fb86f51002fd5ae", size = 213344, upload-time = "2026-07-31T11:29:28.28Z" }, { url = "https://files.pythonhosted.org/packages/83/9e/abfdde9cbd57f0b5867a70e3426ac63341e1a21557b273c39bb6d2ccf8b9/websockets-17.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:1bdd8c4be420905dd732e00dcd669852d8128cc723efa585a0c0e51adb00a28a", size = 213277, upload-time = "2026-07-31T11:29:29.504Z" }, { url = "https://files.pythonhosted.org/packages/50/ff/6199a52d864215750af8668d84b0274775011a90052081f5a9495807a92b/websockets-17.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10f461191125c63902ea7394ae9e752b1b5785641850c1d365bb30b0f88bc53f", size = 212603, upload-time = "2026-07-31T11:29:30.771Z" }, { url = "https://files.pythonhosted.org/packages/54/7e/439a962bcada88dcf586da77a1b2385f91e2d2910e9359540934c827156b/websockets-17.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cffc84ddec6da7f447677266fee2a3c40ecc78172f00752aa1150b8a8d65df1d", size = 210286, upload-time = "2026-07-31T11:29:32.157Z" }, { url = "https://files.pythonhosted.org/packages/d9/82/123660edc759c225626b3b91952c7625f85c77a8362acbc35a4623120f7d/websockets-17.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c23e532c8a2325a1e7486de8763a60dc43e83f01bcaeca07e3ba79652c156db1", size = 210549, upload-time = "2026-07-31T11:29:33.388Z" }, { url = "https://files.pythonhosted.org/packages/cd/2f/2940e57080cf56f28190287516400126d5a76b52b9a61dc10ba6f6400dbe/websockets-17.0.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c09e097d0e46e3c289bedab9a475ae344b70c30ff5646e46af22b4e6fdc97b21", size = 219874, upload-time = "2026-07-31T11:29:34.608Z" }, { url = "https://files.pythonhosted.org/packages/42/28/9ec976c16d63cc51c28dfec74b66854048c0b8b6579946e902f91b69e8bf/websockets-17.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f47b0815af3948ec6a440b3afa02f05b18cc0939549e91b5c677b5d9c2c8472a", size = 220150, upload-time = "2026-07-31T11:29:35.831Z" }, { url = "https://files.pythonhosted.org/packages/f4/a4/850c699a16bbc451723856360c59bd997bec075e637154f3fa96e80d5760/websockets-17.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8848c207049ad49d318e5f64a3d4d7bb189f8328d0d98e65647788f2a085785c", size = 221389, upload-time = "2026-07-31T11:29:37.189Z" }, { url = "https://files.pythonhosted.org/packages/47/e1/f60a891c1a4b3420d5052333a84eb7241e1fb4a71866dec1562f5fa30027/websockets-17.0.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2604de7228506b13a44a256a9d223943340c0e725af5d367dc068e192b027761", size = 224169, upload-time = "2026-07-31T11:29:38.61Z" }, { url = "https://files.pythonhosted.org/packages/26/fb/e2a893be6fae4fddfe50ddc3035a331d3f381103d5467b7900026bdb3a64/websockets-17.0.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07abc3bd196a48af476a82fd47f3f79a6a3f70937a9f930cef703cfa0c9d83b6", size = 222025, upload-time = "2026-07-31T11:29:39.897Z" }, { url = "https://files.pythonhosted.org/packages/d9/72/e3144b2d79276fab9798ed7d4aea2f0847434f186800b6f56a1eddcb3114/websockets-17.0.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:769ce7e2acfd9a89f2bed3a9c0da229459516bbc00bd4c9e2ca492c613ae4861", size = 220779, upload-time = "2026-07-31T11:29:41.084Z" }, { url = "https://files.pythonhosted.org/packages/c0/5e/69c02174fbcf1c40c6adc45d3c316a401558392fe7bab8969ef8c46f1689/websockets-17.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07d78a509c3333f5908c83d7f78144ea68a6c9ec28110f5c54d81d8fcdc262c4", size = 218053, upload-time = "2026-07-31T11:29:42.322Z" }, { url = "https://files.pythonhosted.org/packages/b3/09/7574778b095b99cfa0856583462f56568df784f9b41485145169b2ec9c64/websockets-17.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ffad64ce7ad3703d652a3fd9af26238377d24ce52c6ad8ff35d26d82f61f493f", size = 220825, upload-time = "2026-07-31T11:29:43.553Z" }, { url = "https://files.pythonhosted.org/packages/c5/e6/f46571f38765dbc4cbc0d0b47de8db65768006dbbd4340e6f5f51bc1d895/websockets-17.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e95e321d0d763f2b6633512605f6112ebd70d5746f3ce05c941909d4a25233f2", size = 219427, upload-time = "2026-07-31T11:29:44.731Z" }, { url = "https://files.pythonhosted.org/packages/9d/31/6ff1fee057bd7e9dd5237fc064a749615378d003aa045b5bfc2d12b2f4f7/websockets-17.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd526c8228e759c1006c4b7c9ac71dc4e925ced1a6a6a5a8e94643709738f63e", size = 220198, upload-time = "2026-07-31T11:29:45.997Z" }, { url = "https://files.pythonhosted.org/packages/7a/4f/d41847227a44b9ad87c3d5a9fddbfad8b7c4d6032878d8460d9d37c2d44f/websockets-17.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd3369e42c0246afaf9d669cfc19797e3a49e8c0a639544459c57597108b966", size = 221304, upload-time = "2026-07-31T11:29:47.314Z" }, { url = "https://files.pythonhosted.org/packages/63/30/21a7e326c6ad2eb526cd5b816383d59cdeb28b8805b65a543c3cfbd8e8ce/websockets-17.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b580794e926cab7ff42ee4371ef14e0b22cb2bb722a607f77769136468f49a3f", size = 218858, upload-time = "2026-07-31T11:29:48.587Z" }, { url = "https://files.pythonhosted.org/packages/2d/48/55b0331cd5bec9ce29748f79edc00075805450a47011d0c8e3b1c61dbf04/websockets-17.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5033ffe6804dd53afafa7d08e8c3eef2d2431f34d58ca30507a8442dd04a033a", size = 219840, upload-time = "2026-07-31T11:29:49.791Z" }, { url = "https://files.pythonhosted.org/packages/78/6e/2e8bc06e546f49b32a58a2bc2957902d1809ecc37552d3d7ccd6639a126e/websockets-17.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6db9e5bf3649ab506c6ae8a3ac85a00fb1ae3816d75962771b2df8adbc5d40d2", size = 220116, upload-time = "2026-07-31T11:29:51.026Z" }, { url = "https://files.pythonhosted.org/packages/b0/ad/4bac01fa41aca54307157b9c9f68b066a6bb51fb18716ba618078a67b283/websockets-17.0.1-cp312-cp312-win32.whl", hash = "sha256:bc0bca48ba24c6c866847fd20478a51dd547fa0ad258dab9615c414ec534bbc0", size = 213050, upload-time = "2026-07-31T11:29:52.328Z" }, { url = "https://files.pythonhosted.org/packages/82/d8/c3a78cccc74a554780e9e76e323d5cde891048627025f0f82623e22dc3df/websockets-17.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2b3f3020171202b135ca078e20434977c6b2b02af647130d6980c9e39b9462e3", size = 213348, upload-time = "2026-07-31T11:29:53.891Z" }, { url = "https://files.pythonhosted.org/packages/7b/25/e1b8824bd632c8a5a62d504b61e9e35e470b67e4be0206f5c28f90c7f86d/websockets-17.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:41d6aa06b5ab832aee72fedf47a149535b121ac900b6bb4d3fe14712afac9a79", size = 213276, upload-time = "2026-07-31T11:29:55.299Z" }, { url = "https://files.pythonhosted.org/packages/ba/a8/79c577bc2f874ee22f6f5ccdab97ba9ce6b96806be3fcc3a6d8490f88a21/websockets-17.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:55b12e47dcee83673a40d07686cfb6f9d6dfc285976ade9463f61d2bef3fad22", size = 212593, upload-time = "2026-07-31T11:29:56.518Z" }, { url = "https://files.pythonhosted.org/packages/db/99/e1cfaf419bb3b2fcfd6792a846f1d936293132b0b9a56530ced016c83c7b/websockets-17.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c118a6b0e25bfc9a6802075d748fa6321714ffbdf3c88d29d9a0e3c7386c75", size = 210280, upload-time = "2026-07-31T11:29:57.768Z" }, { url = "https://files.pythonhosted.org/packages/a2/ef/cc994494bf7d97e41833f6ff55c24f535e4d527a10370b9631737e9c2f00/websockets-17.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:734d20364dc2cfe03674883cafcf580b6e431c5ce42b476312b9285310230cf9", size = 210538, upload-time = "2026-07-31T11:29:59.021Z" }, { url = "https://files.pythonhosted.org/packages/87/32/fbf2d132f63ba3e67f675bccf333469786a24e0418969ce1d8e6ff9e6f02/websockets-17.0.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9493314a99e599163c854fb5900ad7f7ea38c5cb9d9103aa30b3c6b8181c01fa", size = 219925, upload-time = "2026-07-31T11:30:00.298Z" }, { url = "https://files.pythonhosted.org/packages/16/50/64eee3d25a47fe744a9490e0627cc373dca096755db740f91c28bd61cd35/websockets-17.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:18ded646ce98cdd3c0235825b3252f1df55765ba49b616bb10282f758667b4d0", size = 220206, upload-time = "2026-07-31T11:30:01.52Z" }, { url = "https://files.pythonhosted.org/packages/15/56/10ed4bc4dd75f204e3c62bd4898e44a8742a27773c80b188cfa7888aad2d/websockets-17.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1bec5d6a19f5fbe87e4940739cfc65e7bb53d8b353e1029b8037a1653b321bc", size = 221445, upload-time = "2026-07-31T11:30:02.788Z" }, { url = "https://files.pythonhosted.org/packages/bf/be/bb14328614c068ab09569962fbf218fc00413ce3febc6d2684c764b6f37e/websockets-17.0.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:872273e629ca7e3d35f16a2dc6ede84e1d5c831e616b8277de6e4f83114e7c58", size = 222887, upload-time = "2026-07-31T11:30:03.943Z" }, { url = "https://files.pythonhosted.org/packages/32/1b/4cb0eec2fee310007104687493175af190019f705940c864f9c523fe9f6f/websockets-17.0.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1df81d174c1561292de9e40b141cafc04f69077272f6c352afe1d743e20810df", size = 222072, upload-time = "2026-07-31T11:30:05.258Z" }, { url = "https://files.pythonhosted.org/packages/6c/9c/14e6391de777ddb39c439c450deb551406d445e25a5877d6fa25c49d4544/websockets-17.0.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:759adeb5b0c5775b563254ec63b5b79089fc0045b479143a0b1b8c0ebaae1253", size = 220826, upload-time = "2026-07-31T11:30:06.53Z" }, { url = "https://files.pythonhosted.org/packages/cb/57/96e94e384442247bbed5d3ab67381c7257355c2d66b62c3ad33a17f5d385/websockets-17.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1d99db29b5444e3982f1ce2ba8a833508ad44b2f1fbd0bd99e81d825c0b461", size = 218107, upload-time = "2026-07-31T11:30:07.766Z" }, { url = "https://files.pythonhosted.org/packages/c0/8c/9c9dedd14c3919435df9b35cdee7111268c751252b87652f3a6a4f56e760/websockets-17.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:02ed63bf26dda9fa27df730a41f6664586c4ee05972c8fb667ce1725b3fd13d3", size = 220889, upload-time = "2026-07-31T11:30:09.035Z" }, { url = "https://files.pythonhosted.org/packages/94/4d/ca73c2ac82c00f50c529784bacb323e42da4816333211bc1543d90c9cf11/websockets-17.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:eab6de8a98b9a7772cf686d00b4de439fc7efb8ab05ae106ef227291d06f87c5", size = 219486, upload-time = "2026-07-31T11:30:10.289Z" }, { url = "https://files.pythonhosted.org/packages/5b/da/fb37ac09dcd7c69dd73bac979ed393df35f78a3c232e293d1ff3bd586d24/websockets-17.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2a855b6dfe21c4d3420be265ae031829ba8ba0be0ea350d9f7c3ef30ae63ebe2", size = 220258, upload-time = "2026-07-31T11:30:11.605Z" }, { url = "https://files.pythonhosted.org/packages/fc/04/9693f191d968a93f37326a17301a101d49580889c688f466699f89ecdee1/websockets-17.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7002d5f9e1c3ddd991cdfdbfee18cc8c8b196b2445022892badacd6cb338bbbc", size = 221358, upload-time = "2026-07-31T11:30:12.858Z" }, { url = "https://files.pythonhosted.org/packages/cf/29/ad0d85c01db5dcf22898d51648bd2c25af0dd0a4a41c550b11acddeeeba7/websockets-17.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c395bda8e7d8f51a02e80261fb57127979e5c472675d9a96b2860619ad47da48", size = 218921, upload-time = "2026-07-31T11:30:14.064Z" }, { url = "https://files.pythonhosted.org/packages/18/3b/bf8e855e495dcca63f2b8aa019cf2ada3160e1fa66d833c7417f3b1f7f38/websockets-17.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aadc298969ad229d8e3029fc5cc751fdad286696230f9cf014e90ff9cd8e6ea0", size = 219871, upload-time = "2026-07-31T11:30:15.358Z" }, { url = "https://files.pythonhosted.org/packages/3b/db/c7abd6639a93a40279cd1ddc57e09e1c4f8381c4cfccdb775aa5aac9770a/websockets-17.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f11a398d8170b7ac5000baf7f258dcda579ef3ea744e0cc6a165e0dfbc0d3198", size = 220154, upload-time = "2026-07-31T11:30:16.96Z" }, { url = "https://files.pythonhosted.org/packages/f6/2a/25a9f8f2e5a6ef34e911d2f55d9f756bdeb92b4c28cfb77b8430bbc73cb1/websockets-17.0.1-cp313-cp313-win32.whl", hash = "sha256:846a4a8b0833e3cad57523d9e3bd50ec8ea05ab9d06c582f82a1340ba096af5f", size = 213038, upload-time = "2026-07-31T11:30:18.434Z" }, { url = "https://files.pythonhosted.org/packages/81/2f/ea1380f72bb11b64fc5bc7ae0d42de5bbf3e6dc13b965706b2a1d4e17cdf/websockets-17.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:409d93efcaa14f7a99592c5baaef5ec6ca94fba0f5aec1a86f693977c69c9c1c", size = 213348, upload-time = "2026-07-31T11:30:19.693Z" }, { url = "https://files.pythonhosted.org/packages/e6/c7/b956ed9151c3c74530ebc62d716fbfdbde7507a6acc6423a64f9ecfb6b8a/websockets-17.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:90246fa9e6cb192a778ce6ce024057ec54317a894db7899c922dcdc1f4cbf6a5", size = 213282, upload-time = "2026-07-31T11:30:21.045Z" }, { url = "https://files.pythonhosted.org/packages/98/dc/cadab608924ac605647031472fb1f8792d7d4ea07565ba1899ec42028e0d/websockets-17.0.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:53b90c00bc6201ab6695c7ff51a04d0e425514c37515e9eeecd2c1b978ac6c0e", size = 212640, upload-time = "2026-07-31T11:30:22.436Z" }, { url = "https://files.pythonhosted.org/packages/16/7a/b034d13ca181211bbd58bb50835cb196a7784cd505b5a2079d4d03374f9f/websockets-17.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f33a649bfcb8312524173cc4bbafa7dbb236e18eee9aa31a1d324ca0ddda28c", size = 210332, upload-time = "2026-07-31T11:30:23.608Z" }, { url = "https://files.pythonhosted.org/packages/2f/4d/943ede39b53744768edf1ed84a3f9401527388228a3d6c1249c02c3d6bd7/websockets-17.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cddc675ec31bca65473321f9a9794e488b43b3b8de5d02c8ef4810c5d5792163", size = 210546, upload-time = "2026-07-31T11:30:24.932Z" }, { url = "https://files.pythonhosted.org/packages/cf/e3/88dc159d2ae66743c669443246243f28d873b0c5e58271b8cc1ca0440334/websockets-17.0.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b3ff0ad440ad52dda64138f16895f66403f40192365e39b1010e889f289746b0", size = 219928, upload-time = "2026-07-31T11:30:26.221Z" }, { url = "https://files.pythonhosted.org/packages/fe/f2/ff27eaefa15851a5cf7f004ab827a022bf2d6632cb520f89cb100db7e84b/websockets-17.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:72d7f2a5aeb4e82daa4ee18f125b4277f427033359be5c745ad709608446cc2c", size = 220279, upload-time = "2026-07-31T11:30:27.49Z" }, { url = "https://files.pythonhosted.org/packages/19/2e/a5166149f363d2449c1cb2dde6486a245521979509d53b87a09f3e79662b/websockets-17.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6fd88365da261c53d3e943fb37e0d0721b9cde119f6b2e3fc84369b6ab234d63", size = 221525, upload-time = "2026-07-31T11:30:28.872Z" }, { url = "https://files.pythonhosted.org/packages/9b/b5/f46931269b3ff3bde65d27c65ddb22f9bb8ce92ac2c6c4df0910128f6219/websockets-17.0.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ab9f962a5b64a5c3c845d556b7dc4e6fb683f7b67179f8205e814bb2e0213ffe", size = 222897, upload-time = "2026-07-31T11:30:30.164Z" }, { url = "https://files.pythonhosted.org/packages/42/f4/deccf3439f35df953ec35e13fe07986821c5f1ab5785d69614283bdb9034/websockets-17.0.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c07f145d0b9e90cbd96035f31fb79199aef4da1872854e36ebeb258e3d57594", size = 222129, upload-time = "2026-07-31T11:30:31.489Z" }, { url = "https://files.pythonhosted.org/packages/35/a5/e1b57a59da92ade37fd021567a17b518ea8267b28e5530075844cdb525fe/websockets-17.0.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f7747d3daa41a11f25f7cca5dc988fc51da97b311bed4c9d843860f79779283", size = 220875, upload-time = "2026-07-31T11:30:32.805Z" }, { url = "https://files.pythonhosted.org/packages/f0/30/e7d0889c790a854156de424575fd67af79ddbaed9ff3157ae863dfd1c1dc/websockets-17.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2abb1ba0a5133b7d2ef3c1c9f4b0c1e8a101012dce0b594ab2b2888d9a64820e", size = 218160, upload-time = "2026-07-31T11:30:34.512Z" }, { url = "https://files.pythonhosted.org/packages/09/2e/43db785d6ed9ae7594fae7b62bbc9cb4dfee2b015e06a1005f1e5ce283b6/websockets-17.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f3fd9a1f87f8f0f3f8e9f9bd0195f7516562d13f5b178db8c5784d1f60b60bed", size = 220951, upload-time = "2026-07-31T11:30:35.806Z" }, { url = "https://files.pythonhosted.org/packages/eb/f5/4ac3cab3d5e8a830657a822f64a8910e3803229c6783e59c3fd9a3487427/websockets-17.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2bc14b481e05e331811108daa1aeb41a5e237a5564ef2f02ec5a356a0f102f78", size = 219460, upload-time = "2026-07-31T11:30:37.273Z" }, { url = "https://files.pythonhosted.org/packages/03/0e/c3a4020673ffc17c82cf1a467835038a196a555d3b4f2a50f0f063cf8ccc/websockets-17.0.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57d2ee9b24b404ce75f3814f92073c0ed88106c950148d2427fe8d25ca254d1f", size = 220248, upload-time = "2026-07-31T11:30:38.527Z" }, { url = "https://files.pythonhosted.org/packages/7d/87/e47a6a278cc1dfade38444c893ce18322943c25d4b780a74450d9d164be1/websockets-17.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1b363bfd72a52c0658a3154a4cff219f15a474b35a235057d38853bf151acce7", size = 221421, upload-time = "2026-07-31T11:30:39.879Z" }, { url = "https://files.pythonhosted.org/packages/da/8f/473d5fc4e3836e375b0233c6ef26777e6e5e3f7bfc84ccd524eae4090ed5/websockets-17.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:10b1587c599fa0f2c89154587c80e0fda98ade6c9fa8c0260a2823fb1800b685", size = 218975, upload-time = "2026-07-31T11:30:41.192Z" }, { url = "https://files.pythonhosted.org/packages/d4/b9/819ec2dcdf69031d7e9cab11247f3a6ff9bbc8c7c53ada1dbcb9055b227b/websockets-17.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d7d72843691f50b91127c50688df10cb72ec6f4c4b1d7e2c11ab33b16acf8e51", size = 219925, upload-time = "2026-07-31T11:30:42.524Z" }, { url = "https://files.pythonhosted.org/packages/85/b9/6c0da301f6118502e079cf92f4e864adf28e56b3f8c0f6085076ced7b876/websockets-17.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90973a3a00f23afdfd1c9b06fb84289bf0220f247ef8a62501a1967c7af54f7b", size = 220218, upload-time = "2026-07-31T11:30:44.04Z" }, { url = "https://files.pythonhosted.org/packages/55/f9/cba32dc9dd856565263d6272f594255bd0e2781deb8cd982c026a54760ad/websockets-17.0.1-cp314-cp314-win32.whl", hash = "sha256:599b03beb77633bffc095334338fad79cafc2b01fbd58953838130a9ae967d7b", size = 212626, upload-time = "2026-07-31T11:30:45.579Z" }, { url = "https://files.pythonhosted.org/packages/fa/95/91cdd8c192287d7ea741f37cf7d64fdc1a14410f06f73805e428a1a590af/websockets-17.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:81ce19c6046ace11da7001781be7317bb1dc389f399af4b2ed962190f76f9add", size = 212969, upload-time = "2026-07-31T11:30:46.983Z" }, { url = "https://files.pythonhosted.org/packages/8e/fd/8c98a1e431960661c5769ab1a4dd66494e87ab02d791cc79e51e0d9a289f/websockets-17.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:efe0ae052a8d023b87198921e8a7ce1dc7768816bcd2fbc20df171ac73a04891", size = 212850, upload-time = "2026-07-31T11:30:48.304Z" }, { url = "https://files.pythonhosted.org/packages/13/c1/142f5186ee7dc3beee0426b998a79e223e067b7689afcaa95890d64aa800/websockets-17.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ab56439c9f74c52770690c7b2f616b3bf775cb3920453ee355ac765c032d8bbf", size = 212967, upload-time = "2026-07-31T11:30:49.688Z" }, { url = "https://files.pythonhosted.org/packages/02/de/4b03ed316c9dee180365286c298219809ff247be39beeaaf9958b21167ab/websockets-17.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:20a92f78ac8250984ed459faa9ca48c285adbfc0038ddc3fdac6046990a9c9ed", size = 210504, upload-time = "2026-07-31T11:30:50.987Z" }, { url = "https://files.pythonhosted.org/packages/cd/d8/ad2b3e8f867e1e8cac3077e2f33ffb60b71bd763d6cfc71bd916f113c3bf/websockets-17.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6a434e59962a4fb9016bea327e1d14d6cd67670ecfb8942b4f4a0c24036634ce", size = 210702, upload-time = "2026-07-31T11:30:52.261Z" }, { url = "https://files.pythonhosted.org/packages/48/18/7a77a82ce9d6f831c07b176da3942f7e71acd0f115f3ecdb1d00a040eb01/websockets-17.0.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2503c7e2a5049a12d5dac917a46d5d52591283a766165b8176bb167560421b38", size = 220290, upload-time = "2026-07-31T11:30:53.582Z" }, { url = "https://files.pythonhosted.org/packages/f5/af/43c3e3c3ea7ba4693c2181743f3221957df28bededadcbd9fc8a0661bde0/websockets-17.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:28012a54510fe8301bb893ef143cec30a2780a2d3bc20b7bbdf4379d7a63945d", size = 220573, upload-time = "2026-07-31T11:30:54.966Z" }, { url = "https://files.pythonhosted.org/packages/1f/bd/ed48eca15725743ee7e2dc172e15c61de29e85ec98dace7b14257f366836/websockets-17.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22bd00f8bae2bccdb5dbe41e20f58ba44ca9fff0b4b561aaf39099c35da762ed", size = 221747, upload-time = "2026-07-31T11:30:56.762Z" }, { url = "https://files.pythonhosted.org/packages/99/50/838deb7937a8225c4925dd4a977eafea473fabf444178a99de0bc7e92bb0/websockets-17.0.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e98ec9ec61cce5bc4b8b218322ad090b0994eb060bb04da704c62ef0a3d864e6", size = 223891, upload-time = "2026-07-31T11:30:58.127Z" }, { url = "https://files.pythonhosted.org/packages/f8/e9/657fb70c6eb6bcd01adfa5d2b06496e9911e1c1a8813d353b8c00f7591cd/websockets-17.0.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e387adb0c692c6b5571bdeafc8ac9d1901ea30f10309134780b16ecd35e6605", size = 222317, upload-time = "2026-07-31T11:30:59.416Z" }, { url = "https://files.pythonhosted.org/packages/07/4c/82cb722afa5428fed981331210c4c07600570db01bb1620579f655b5adaf/websockets-17.0.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd1470d2c53fe53269bf5619da7725d30dd9b9693f1689f7a85eab8dea734442", size = 221047, upload-time = "2026-07-31T11:31:00.757Z" }, { url = "https://files.pythonhosted.org/packages/90/84/bd6d67d6bc65f0de0cb50de55dab42f256a9876a351c4736522eb168fda0/websockets-17.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:884af729b8ab50486acd94d9768c2b60914bf39b579ebba0a5cb73bfdfd61fd2", size = 218626, upload-time = "2026-07-31T11:31:02.48Z" }, { url = "https://files.pythonhosted.org/packages/96/cb/6a372c8553976f0d8f97f5115826ed47e34b3be6b8bf0d0249af249a7416/websockets-17.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a60fa1a25cca1bcc2bf87b8d6be37a741f0a3239fb5e9cfb7a37173b68ffcf87", size = 221299, upload-time = "2026-07-31T11:31:03.795Z" }, { url = "https://files.pythonhosted.org/packages/bc/31/f966e8472337974f74d788b3ef6c6f3b8b9a5f201efd16a843c91d269fa5/websockets-17.0.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e8208f2729cba030ff872a92064c97584eeb9502f53d32a05a0f05d5a17ca6c6", size = 219789, upload-time = "2026-07-31T11:31:05.08Z" }, { url = "https://files.pythonhosted.org/packages/57/34/404e83a6cc7b0efcac810b7041bffd72ff76900e6fd0aa45a26c92fb2ffe/websockets-17.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:54cdcaa56f5d3eafd57058f0fa4a3de93a310b43a3c4699f06efc4c0bd054a5a", size = 220678, upload-time = "2026-07-31T11:31:06.635Z" }, { url = "https://files.pythonhosted.org/packages/e5/70/8946188c2a68d67251859b589a3634918cf7867bf0b891347a5ecaa43d30/websockets-17.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4d41c0a1d47a478bc432b3b9068097bee1ce0c5b19327ea6f75c2ab34ab1f2fb", size = 221697, upload-time = "2026-07-31T11:31:08.023Z" }, { url = "https://files.pythonhosted.org/packages/04/16/ee73fc2083a2938ac6209f4ec804960496835b20a0068dbcfe8424957c04/websockets-17.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f991247276797d0c61ab7770bc9791eadc16f683b4d83517f624932adc1a8bab", size = 219390, upload-time = "2026-07-31T11:31:09.378Z" }, { url = "https://files.pythonhosted.org/packages/94/6a/d5f88033c69932af6cdaa72da62516ade47c257e3bf69f4c0ba5f40e12a2/websockets-17.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:733e3cc7171fa1b899edbe725ef9382d0e960657dc1fd933f3281ae910c01dab", size = 220161, upload-time = "2026-07-31T11:31:10.915Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/6183dd2c0370287ecf4afe0bb33aca364208e5e7b0e1a286adcaecc0c78b/websockets-17.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:810cb3fb5fa6e447216f4e82d9a85cb8aed0929ae3538153ddfe8a6e3121a58d", size = 220591, upload-time = "2026-07-31T11:31:12.289Z" }, { url = "https://files.pythonhosted.org/packages/26/93/70f6516d85b9744f7eac224c4b1b9ef4e84133f80b53be02080cb1c3e663/websockets-17.0.1-cp314-cp314t-win32.whl", hash = "sha256:17ac37716c0244e82c9e384c41653c090b1864c6610224ca3857e7f7b58fce10", size = 212755, upload-time = "2026-07-31T11:31:13.883Z" }, { url = "https://files.pythonhosted.org/packages/f1/2c/9d9c1da5a7ea9af307b386d25f64d1dead4729644198d2b92e36db5dfd41/websockets-17.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bb31f42ea095ea826463c770829aa188a86c9a5c976b1467cbbf583c811de833", size = 213094, upload-time = "2026-07-31T11:31:15.367Z" }, { url = "https://files.pythonhosted.org/packages/5b/24/a585e7573e128070605d003b5544729bcd58d9756c7e99d550818ca4b916/websockets-17.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b", size = 213009, upload-time = "2026-07-31T11:31:16.776Z" }, { url = "https://files.pythonhosted.org/packages/51/77/63b4abd29f15107d856f010de6f35434faa7c49ef89151e051d2807a9c40/websockets-17.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:49266e4488309b38783257293a38298942b9a03aa106fcb45195377a77c0c1e2", size = 210194, upload-time = "2026-07-31T11:31:18.046Z" }, { url = "https://files.pythonhosted.org/packages/ef/ab/6160542ee644f72b865af13ddfff23740c95595b237e16312262cafdb641/websockets-17.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d69fd559f9f0e8a52d2fce6f04ee143f86e70df0a189cd95164eddac599e810f", size = 210465, upload-time = "2026-07-31T11:31:19.333Z" }, { url = "https://files.pythonhosted.org/packages/53/1a/d4437d3cb0691eeac2c6064e21c83a9eeda04f6ef0261abfc0dde708590d/websockets-17.0.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a39ce3a7b0e6059be093213d637963101380157bcbad355916738fafb490698d", size = 211417, upload-time = "2026-07-31T11:31:20.687Z" }, { url = "https://files.pythonhosted.org/packages/88/28/2d671e23a20359a1cc142848384659813b49028d46cb0acdc28e81ac59b5/websockets-17.0.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2437d4ca208cc0f246d3a2297ae7474b4ba18261aaf5b9c79c84c031ecf348e1", size = 211309, upload-time = "2026-07-31T11:31:21.969Z" }, { url = "https://files.pythonhosted.org/packages/02/52/b85a676b161991e5c0d884252376435f60510789e7740e3da73489d22a6e/websockets-17.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6740be6d1bab69f08ab52cb15b08f76c143b6fe61c580ba62bd929f3ab7a1d42", size = 212203, upload-time = "2026-07-31T11:31:23.38Z" }, { url = "https://files.pythonhosted.org/packages/bf/e3/91e297e41381d9131f3142a9c1a50389fd96b98125ce9b81526fa4e14b9f/websockets-17.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:afbce6e3f0fac32dc87c2a0d84869d1a706460d64f39f3889386413e6e4d3d26", size = 213434, upload-time = "2026-07-31T11:31:24.707Z" }, { url = "https://files.pythonhosted.org/packages/09/ce/3929538b2b9918f5eee623fbf3346893973191f6df93f19bbda097bd7bb7/websockets-17.0.1-py3-none-any.whl", hash = "sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345", size = 206718, upload-time = "2026-07-31T11:31:26.037Z" }, ] [[package]] name = "werkzeug" version = "3.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, ]