├── tests ├── __init__.py ├── testapp │ ├── __init__.py │ ├── urls.py │ └── views.py ├── settings.py └── test_django_permissions_policy.py ├── src └── django_permissions_policy │ ├── py.typed │ └── __init__.py ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── issue.yml ├── SECURITY.md ├── CODE_OF_CONDUCT.md ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── HISTORY.rst ├── MANIFEST.in ├── .editorconfig ├── .typos.toml ├── tox.ini ├── LICENSE ├── .pre-commit-config.yaml ├── pyproject.toml ├── README.rst ├── CHANGELOG.rst └── uv.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testapp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/django_permissions_policy/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | Please report security issues directly over email to me@adamj.eu 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.pyc 3 | /.coverage 4 | /.coverage.* 5 | /.tox 6 | /build/ 7 | /dist/ 8 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | See https://github.com/adamchainz/django-permissions-policy/blob/main/CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | This project follows [Django's Code of Conduct](https://www.djangoproject.com/conduct/). 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune tests 2 | include CHANGELOG.rst 3 | include LICENSE 4 | include pyproject.toml 5 | include README.rst 6 | include src/*/py.typed 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | groups: 6 | "GitHub Actions": 7 | patterns: 8 | - "*" 9 | schedule: 10 | interval: monthly 11 | -------------------------------------------------------------------------------- /tests/testapp/urls.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from django.urls import path 4 | 5 | from tests.testapp import views 6 | 7 | urlpatterns = [ 8 | path("", views.index), 9 | path("async/", views.async_index), 10 | ] 11 | -------------------------------------------------------------------------------- /tests/testapp/views.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from django.http import HttpResponse 4 | 5 | 6 | def index(request): 7 | return HttpResponse("Hello World") 8 | 9 | 10 | async def async_index(request): 11 | return HttpResponse("Hello World") 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.py] 14 | indent_size = 4 15 | 16 | [Makefile] 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request an enhancement or new feature. 3 | body: 4 | - type: textarea 5 | id: description 6 | attributes: 7 | label: Description 8 | description: Please describe your feature request with appropriate detail. 9 | validations: 10 | required: true 11 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | # Configuration file for 'typos' tool 2 | # https://github.com/crate-ci/typos 3 | 4 | [default] 5 | extend-ignore-re = [ 6 | # Single line ignore comments 7 | "(?Rm)^.*(#|//)\\s*typos: ignore$", 8 | # Multi-line ignore comments 9 | "(?s)(#|//)\\s*typos: off.*?\\n\\s*(#|//)\\s*typos: on" 10 | ] 11 | 12 | [default.extend-words] 13 | ect = "ect" 14 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | DEBUG = False 6 | 7 | SECRET_KEY = "not-secret" 8 | 9 | DATABASES = { 10 | "default": { 11 | "ENGINE": "django.db.backends.sqlite3", 12 | "LOCATION": ":memory:", 13 | } 14 | } 15 | 16 | ALLOWED_HOSTS = ["*"] 17 | 18 | INSTALLED_APPS: list[str] = [] 19 | 20 | MIDDLEWARE = [ 21 | "django.middleware.security.SecurityMiddleware", 22 | "django_permissions_policy.PermissionsPolicyMiddleware", 23 | "django.middleware.common.CommonMiddleware", 24 | ] 25 | 26 | ROOT_URLCONF = "tests.testapp.urls" 27 | 28 | STATIC_URL = "/static/" 29 | 30 | TEMPLATES: list[dict[str, Any]] = [] 31 | 32 | USE_TZ = True 33 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | requires = 3 | tox>=4.2 4 | env_list = 5 | py314-django{60, 52} 6 | py313-django{60, 52, 51} 7 | py312-django{60, 52, 51, 50, 42} 8 | py311-django{52, 51, 50, 42} 9 | py310-django{52, 51, 50, 42} 10 | 11 | [testenv] 12 | runner = uv-venv-lock-runner 13 | package = wheel 14 | wheel_build_env = .pkg 15 | set_env = 16 | PYTHONDEVMODE = 1 17 | commands = 18 | python \ 19 | -W error::ResourceWarning \ 20 | -W error::DeprecationWarning \ 21 | -W error::PendingDeprecationWarning \ 22 | -m coverage run \ 23 | -m pytest {posargs:tests} 24 | dependency_groups = 25 | test 26 | django42: django42 27 | django50: django50 28 | django51: django51 29 | django52: django52 30 | django60: django60 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.yml: -------------------------------------------------------------------------------- 1 | name: Issue 2 | description: File an issue 3 | body: 4 | - type: input 5 | id: python_version 6 | attributes: 7 | label: Python Version 8 | description: Which version of Python were you using? 9 | placeholder: 3.14.0 10 | validations: 11 | required: false 12 | - type: input 13 | id: django_version 14 | attributes: 15 | label: Django Version 16 | description: Which version of Django were you using? 17 | placeholder: 3.2.0 18 | validations: 19 | required: false 20 | - type: input 21 | id: package_version 22 | attributes: 23 | label: Package Version 24 | description: Which version of this package were you using? If not the latest version, please check this issue has not since been resolved. 25 | placeholder: 1.0.0 26 | validations: 27 | required: false 28 | - type: textarea 29 | id: description 30 | attributes: 31 | label: Description 32 | description: Please describe your issue. 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Adam Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_schedule: monthly 3 | 4 | default_language_version: 5 | python: python3.13 6 | 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 10 | hooks: 11 | - id: check-added-large-files 12 | - id: check-case-conflict 13 | - id: check-json 14 | - id: check-merge-conflict 15 | - id: check-symlinks 16 | - id: check-toml 17 | - id: end-of-file-fixer 18 | - id: trailing-whitespace 19 | - repo: https://github.com/crate-ci/typos 20 | rev: 802d5794ff9cf7b15610c47eca99cd1ab757d8d4 # frozen: v1 21 | hooks: 22 | - id: typos 23 | - repo: https://github.com/tox-dev/pyproject-fmt 24 | rev: d252a2a7678b47d1f2eea2f6b846ddfdcd012759 # frozen: v2.11.1 25 | hooks: 26 | - id: pyproject-fmt 27 | - repo: https://github.com/tox-dev/tox-ini-fmt 28 | rev: be26ee0d710a48f7c1acc1291d84082036207bd3 # frozen: 1.7.0 29 | hooks: 30 | - id: tox-ini-fmt 31 | - repo: https://github.com/rstcheck/rstcheck 32 | rev: 27258fde1ee7d3b1e6a7bbc58f4c7b1dd0e719e5 # frozen: v6.2.5 33 | hooks: 34 | - id: rstcheck 35 | additional_dependencies: 36 | - tomli==2.0.1 37 | - repo: https://github.com/adamchainz/django-upgrade 38 | rev: 553731fe59437e0bd2cf18b10144116422bed259 # frozen: 1.29.1 39 | hooks: 40 | - id: django-upgrade 41 | - repo: https://github.com/adamchainz/blacken-docs 42 | rev: dda8db18cfc68df532abf33b185ecd12d5b7b326 # frozen: 1.20.0 43 | hooks: 44 | - id: blacken-docs 45 | additional_dependencies: 46 | - black==25.1.0 47 | - repo: https://github.com/astral-sh/ruff-pre-commit 48 | rev: 36243b70e5ce219623c3503f5afba0f8c96fda55 # frozen: v0.14.7 49 | hooks: 50 | - id: ruff-check 51 | args: [ --fix ] 52 | - id: ruff-format 53 | - repo: https://github.com/pre-commit/mirrors-mypy 54 | rev: c2738302f5cf2bfb559c1f210950badb133613ea # frozen: v1.19.0 55 | hooks: 56 | - id: mypy 57 | additional_dependencies: 58 | - django-stubs==5.1.2 59 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '**' 9 | pull_request: 10 | 11 | concurrency: 12 | group: ${{ github.head_ref || github.run_id }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | tests: 17 | name: Python ${{ matrix.python-version }} 18 | runs-on: ubuntu-24.04 19 | 20 | strategy: 21 | matrix: 22 | python-version: 23 | - '3.10' 24 | - '3.11' 25 | - '3.12' 26 | - '3.13' 27 | - '3.14' 28 | 29 | steps: 30 | - uses: actions/checkout@v6 31 | 32 | - uses: actions/setup-python@v6 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | allow-prereleases: true 36 | 37 | - name: Install uv 38 | uses: astral-sh/setup-uv@v7 39 | with: 40 | enable-cache: true 41 | 42 | - name: Run tox targets for ${{ matrix.python-version }} 43 | run: uvx --with tox-uv tox run -f py$(echo ${{ matrix.python-version }} | tr -d .) 44 | 45 | - name: Upload coverage data 46 | uses: actions/upload-artifact@v5 47 | with: 48 | name: coverage-data-${{ matrix.python-version }} 49 | path: '${{ github.workspace }}/.coverage.*' 50 | include-hidden-files: true 51 | if-no-files-found: error 52 | 53 | coverage: 54 | name: Coverage 55 | runs-on: ubuntu-24.04 56 | needs: tests 57 | steps: 58 | - uses: actions/checkout@v6 59 | 60 | - uses: actions/setup-python@v6 61 | with: 62 | python-version: '3.13' 63 | 64 | - name: Install uv 65 | uses: astral-sh/setup-uv@v7 66 | 67 | - name: Install dependencies 68 | run: uv pip install --system coverage[toml] 69 | 70 | - name: Download data 71 | uses: actions/download-artifact@v6 72 | with: 73 | path: ${{ github.workspace }} 74 | pattern: coverage-data-* 75 | merge-multiple: true 76 | 77 | - name: Combine coverage and fail if it's <100% 78 | run: | 79 | python -m coverage combine 80 | python -m coverage html --skip-covered --skip-empty 81 | python -m coverage report --fail-under=100 82 | echo "## Coverage summary" >> $GITHUB_STEP_SUMMARY 83 | python -m coverage report --format=markdown >> $GITHUB_STEP_SUMMARY 84 | 85 | - name: Upload HTML report 86 | if: ${{ failure() }} 87 | uses: actions/upload-artifact@v5 88 | with: 89 | name: html-report 90 | path: htmlcov 91 | 92 | release: 93 | needs: [coverage] 94 | if: success() && startsWith(github.ref, 'refs/tags/') 95 | runs-on: ubuntu-24.04 96 | environment: release 97 | 98 | permissions: 99 | contents: read 100 | id-token: write 101 | 102 | steps: 103 | - uses: actions/checkout@v6 104 | 105 | - uses: astral-sh/setup-uv@v7 106 | 107 | - name: Build 108 | run: uv build 109 | 110 | - uses: pypa/gh-action-pypi-publish@release/v1 111 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = [ 4 | "setuptools>=77", 5 | ] 6 | 7 | [project] 8 | name = "django-permissions-policy" 9 | version = "4.28.0" 10 | description = "Set the Permissions-Policy HTTP header on your Django app." 11 | readme = "README.rst" 12 | keywords = [ 13 | "Django", 14 | ] 15 | license = "MIT" 16 | license-files = [ "LICENSE" ] 17 | authors = [ 18 | { name = "Adam Johnson", email = "me@adamj.eu" }, 19 | ] 20 | requires-python = ">=3.10" 21 | classifiers = [ 22 | "Development Status :: 5 - Production/Stable", 23 | "Framework :: Django :: 4.2", 24 | "Framework :: Django :: 5.0", 25 | "Framework :: Django :: 5.1", 26 | "Framework :: Django :: 5.2", 27 | "Framework :: Django :: 6.0", 28 | "Intended Audience :: Developers", 29 | "Natural Language :: English", 30 | "Operating System :: OS Independent", 31 | "Programming Language :: Python :: 3 :: Only", 32 | "Programming Language :: Python :: 3.10", 33 | "Programming Language :: Python :: 3.11", 34 | "Programming Language :: Python :: 3.12", 35 | "Programming Language :: Python :: 3.13", 36 | "Programming Language :: Python :: 3.14", 37 | "Programming Language :: Python :: Implementation :: CPython", 38 | "Typing :: Typed", 39 | ] 40 | dependencies = [ 41 | "asgiref>=3.6", 42 | "django>=4.2", 43 | ] 44 | urls = { Changelog = "https://github.com/adamchainz/django-permissions-policy/blob/main/CHANGELOG.rst", Funding = "https://adamj.eu/books/", Repository = "https://github.com/adamchainz/django-permissions-policy" } 45 | 46 | [dependency-groups] 47 | test = [ 48 | "coverage[toml]", 49 | "pytest", 50 | "pytest-django", 51 | "pytest-randomly", 52 | ] 53 | django42 = [ "django>=4.2a1,<5; python_version>='3.8'" ] 54 | django50 = [ "django>=5a1,<5.1; python_version>='3.10'" ] 55 | django51 = [ "django>=5.1a1,<5.2; python_version>='3.10'" ] 56 | django52 = [ "django>=5.2a1,<6; python_version>='3.10'" ] 57 | django60 = [ "django>=6a1,<6.1; python_version>='3.12'" ] 58 | 59 | [tool.uv] 60 | conflicts = [ 61 | [ 62 | { group = "django42" }, 63 | { group = "django50" }, 64 | { group = "django51" }, 65 | { group = "django52" }, 66 | { group = "django60" }, 67 | ], 68 | ] 69 | 70 | [tool.ruff] 71 | lint.select = [ 72 | # flake8-bugbear 73 | "B", 74 | # flake8-comprehensions 75 | "C4", 76 | # pycodestyle 77 | "E", 78 | # Pyflakes errors 79 | "F", 80 | # isort 81 | "I", 82 | # flake8-simplify 83 | "SIM", 84 | # flake8-tidy-imports 85 | "TID", 86 | # pyupgrade 87 | "UP", 88 | # Pyflakes warnings 89 | "W", 90 | ] 91 | lint.ignore = [ 92 | # flake8-bugbear opinionated rules 93 | "B9", 94 | # line-too-long 95 | "E501", 96 | # suppressible-exception 97 | "SIM105", 98 | # if-else-block-instead-of-if-exp 99 | "SIM108", 100 | ] 101 | lint.extend-safe-fixes = [ 102 | # non-pep585-annotation 103 | "UP006", 104 | ] 105 | lint.isort.required-imports = [ "from __future__ import annotations" ] 106 | 107 | [tool.pyproject-fmt] 108 | max_supported_python = "3.14" 109 | 110 | [tool.pytest.ini_options] 111 | addopts = """\ 112 | --strict-config 113 | --strict-markers 114 | --ds=tests.settings 115 | """ 116 | django_find_project = false 117 | xfail_strict = true 118 | 119 | [tool.coverage.run] 120 | branch = true 121 | parallel = true 122 | source = [ 123 | "django_permissions_policy", 124 | "tests", 125 | ] 126 | 127 | [tool.coverage.paths] 128 | source = [ 129 | "src", 130 | ".tox/**/site-packages", 131 | ] 132 | 133 | [tool.coverage.report] 134 | show_missing = true 135 | 136 | [tool.mypy] 137 | enable_error_code = [ 138 | "ignore-without-code", 139 | "redundant-expr", 140 | "truthy-bool", 141 | ] 142 | mypy_path = "src/" 143 | namespace_packages = false 144 | strict = true 145 | warn_unreachable = true 146 | 147 | [[tool.mypy.overrides]] 148 | module = "tests.*" 149 | allow_untyped_defs = true 150 | 151 | [tool.rstcheck] 152 | report_level = "ERROR" 153 | -------------------------------------------------------------------------------- /tests/test_django_permissions_policy.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from http import HTTPStatus 4 | 5 | import pytest 6 | from django.core.exceptions import ImproperlyConfigured 7 | from django.test import RequestFactory, SimpleTestCase, override_settings 8 | 9 | 10 | class PermissionsPolicyMiddlewareTests(SimpleTestCase): 11 | request_factory = RequestFactory() 12 | 13 | def test_index(self): 14 | resp = self.client.get("/") 15 | 16 | assert resp.status_code == HTTPStatus.OK 17 | assert resp.content == b"Hello World" 18 | 19 | def test_no_setting(self): 20 | resp = self.client.get("/") 21 | 22 | assert "Permissions-Policy" not in resp 23 | 24 | def test_empty_setting(self): 25 | with override_settings(PERMISSIONS_POLICY={}): 26 | resp = self.client.get("/") 27 | 28 | assert "Permissions-Policy" not in resp 29 | 30 | def test_anyone_can_geolocate_list(self): 31 | with override_settings(PERMISSIONS_POLICY={"geolocation": ["*"]}): 32 | resp = self.client.get("/") 33 | 34 | assert resp["Permissions-Policy"] == "geolocation=(*)" 35 | 36 | def test_no_one_can_geolocate(self): 37 | with override_settings(PERMISSIONS_POLICY={"geolocation": []}): 38 | resp = self.client.get("/") 39 | 40 | assert resp["Permissions-Policy"] == "geolocation=()" 41 | 42 | def test_no_one_can_geolocate_old_none_value(self): 43 | with override_settings(PERMISSIONS_POLICY={"geolocation": "none"}): 44 | resp = self.client.get("/") 45 | 46 | assert resp["Permissions-Policy"] == "geolocation=()" 47 | 48 | def test_self_can_geolocate(self): 49 | with override_settings(PERMISSIONS_POLICY={"geolocation": "self"}): 50 | resp = self.client.get("/") 51 | 52 | assert resp["Permissions-Policy"] == "geolocation=(self)" 53 | 54 | def test_example_com_can_geolocate(self): 55 | with override_settings( 56 | PERMISSIONS_POLICY={"geolocation": "https://example.com"} 57 | ): 58 | resp = self.client.get("/") 59 | 60 | assert resp["Permissions-Policy"] == 'geolocation=("https://example.com")' 61 | 62 | def test_multiple_allowed(self): 63 | with override_settings( 64 | PERMISSIONS_POLICY={"autoplay": ["self", "https://example.com"]} 65 | ): 66 | resp = self.client.get("/") 67 | 68 | assert resp["Permissions-Policy"] == 'autoplay=(self "https://example.com")' 69 | 70 | def test_multiple_features(self): 71 | with override_settings( 72 | PERMISSIONS_POLICY={ 73 | "accelerometer": "self", 74 | "geolocation": ["self", "https://example.com"], 75 | } 76 | ): 77 | resp = self.client.get("/") 78 | 79 | assert ( 80 | resp["Permissions-Policy"] 81 | == 'accelerometer=(self), geolocation=(self "https://example.com")' 82 | ) 83 | 84 | def test_unknown_feature(self): 85 | with ( 86 | override_settings(PERMISSIONS_POLICY={"accelerometor": "self"}), 87 | pytest.raises(ImproperlyConfigured), 88 | ): 89 | self.client.get("/") 90 | 91 | def test_setting_changing(self): 92 | with override_settings(PERMISSIONS_POLICY={}): 93 | self.client.get("/") # Forces middleware instantiation 94 | 95 | with override_settings(PERMISSIONS_POLICY={"geolocation": "self"}): 96 | resp = self.client.get("/") 97 | 98 | assert resp["Permissions-Policy"] == "geolocation=(self)" 99 | 100 | def test_other_setting_changing(self): 101 | with override_settings(PERMISSIONS_POLICY={"geolocation": "self"}): 102 | self.client.get("/") # Forces middleware instantiation 103 | 104 | with override_settings(SECRET_KEY="foobar"): 105 | resp = self.client.get("/") 106 | 107 | assert resp["Permissions-Policy"] == "geolocation=(self)" 108 | 109 | async def test_async(self): 110 | with override_settings(PERMISSIONS_POLICY={"geolocation": "self"}): 111 | resp = await self.async_client.get("/async/") 112 | 113 | assert resp.status_code == HTTPStatus.OK 114 | assert resp["Permissions-Policy"] == "geolocation=(self)" 115 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | django-permissions-policy 3 | ========================= 4 | 5 | .. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-permissions-policy/main.yml.svg?branch=main&style=for-the-badge 6 | :target: https://github.com/adamchainz/django-permissions-policy/actions?workflow=CI 7 | 8 | .. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge 9 | :target: https://github.com/adamchainz/django-permissions-policy/actions?workflow=CI 10 | 11 | .. image:: https://img.shields.io/pypi/v/django-permissions-policy.svg?style=for-the-badge 12 | :target: https://pypi.org/project/django-permissions-policy/ 13 | 14 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge 15 | :target: https://github.com/psf/black 16 | 17 | .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge 18 | :target: https://github.com/pre-commit/pre-commit 19 | :alt: pre-commit 20 | 21 | ---- 22 | 23 | Set the ``Permissions-Policy`` HTTP header on your Django app. 24 | 25 | ---- 26 | 27 | **Work smarter and faster** with my book `Boost Your Django DX `__ which covers many ways to improve your development experience. 28 | 29 | ---- 30 | 31 | Requirements 32 | ------------ 33 | 34 | Python 3.10 to 3.14 supported. 35 | 36 | Django 4.2 to 6.0 supported. 37 | 38 | Installation 39 | ------------ 40 | 41 | 1. Install with **pip**: 42 | 43 | .. code-block:: sh 44 | 45 | python -m pip install django-permissions-policy 46 | 47 | 2. Add the middleware in your ``MIDDLEWARE`` setting. It’s best to add it 48 | after Django's ``SecurityMiddleware``, so it adds the header at the same point 49 | in your stack: 50 | 51 | .. code-block:: python 52 | 53 | MIDDLEWARE = [ 54 | ..., 55 | "django.middleware.security.SecurityMiddleware", 56 | "django_permissions_policy.PermissionsPolicyMiddleware", 57 | ..., 58 | ] 59 | 60 | 3. Add the ``PERMISSIONS_POLICY`` setting to your settings, naming at least one 61 | feature. Here’s an example that sets a strict policy to disable many 62 | potentially privacy-invading and annoying features for all scripts: 63 | 64 | .. code-block:: python 65 | 66 | PERMISSIONS_POLICY = { 67 | "accelerometer": [], 68 | "ambient-light-sensor": [], 69 | "autoplay": [], 70 | "camera": [], 71 | "display-capture": [], 72 | "encrypted-media": [], 73 | "fullscreen": [], 74 | "geolocation": [], 75 | "gyroscope": [], 76 | "interest-cohort": [], 77 | "magnetometer": [], 78 | "microphone": [], 79 | "midi": [], 80 | "payment": [], 81 | "usb": [], 82 | } 83 | 84 | See below for more information on the setting. 85 | 86 | Setting 87 | ------- 88 | 89 | Change the ``PERMISSIONS_POLICY`` setting to configure the contents of the 90 | header. 91 | 92 | The setting should be a dictionary laid out with: 93 | 94 | * Keys as the names of browser features - a full list is available on the 95 | `W3 Spec repository`_. The `MDN article`_ is also worth reading. 96 | * Values as lists of strings, where each string is either an origin, e.g. 97 | ``'https://example.com'``, or of the special values ``'self'`` or ``'*'``. If 98 | there is just one value, no containing list is necessary. To represent no 99 | origins being allowed, use an empty list. 100 | 101 | Note that in the header, domains are wrapped in double quotes - do not 102 | include these quotes within your Python string, as they will be added by the 103 | middleware. 104 | 105 | .. _W3 Spec repository: https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md 106 | .. _MDN article: https://developer.mozilla.org/en-US/docs/Web/HTTP/Feature_Policy#Browser_compatibility 107 | 108 | If the keys or values are invalid, ``ImproperlyConfigured`` will be raised at instantiation time, or when processing a response. 109 | The current feature list is pulled from the JavaScript API with ``document.featurePolicy.allowedFeatures()`` on Chrome and Firefox. 110 | Browsers don’t always recognize all features, depending on the version and configuration. 111 | You may see warnings in the console for unavailable features in the header - these are normally safe to ignore, since django-permissions-policy already validates that you don’t have completely unknown names. 112 | 113 | For backwards compatibility with old configuration, the value ``'none'`` is 114 | supported in lists, but ignored - it's preferable to use the empty list 115 | instead. It doesn't make sense to specify ``'none'`` alongside other values. 116 | 117 | Examples 118 | ~~~~~~~~ 119 | 120 | Disable geolocation entirely, for the current origin and any iframes: 121 | 122 | .. code-block:: python 123 | 124 | PERMISSIONS_POLICY = { 125 | "geolocation": [], 126 | } 127 | 128 | Allow autoplay from only the current origin and iframes from 129 | ``https://archive.org``: 130 | 131 | .. code-block:: python 132 | 133 | PERMISSIONS_POLICY = { 134 | "autoplay": ["self", "https://archive.org"], 135 | } 136 | 137 | Allow autoplay from all origins: 138 | 139 | .. code-block:: python 140 | 141 | PERMISSIONS_POLICY = { 142 | "autoplay": "*", 143 | } 144 | -------------------------------------------------------------------------------- /src/django_permissions_policy/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections.abc import Awaitable, Callable 4 | 5 | from asgiref.sync import iscoroutinefunction, markcoroutinefunction 6 | from django.conf import settings 7 | from django.core.exceptions import ImproperlyConfigured 8 | from django.core.signals import setting_changed 9 | from django.dispatch import receiver 10 | from django.http import HttpRequest 11 | from django.http.response import HttpResponseBase 12 | from django.utils.functional import cached_property 13 | 14 | _FEATURE_NAMES: set[str] = { 15 | # Base and Chrome-only features 16 | # Retrieved from Chrome document.featurePolicy.allowedFeatures() 17 | # with flag "Experimental Web Platform features" turned on: 18 | "accelerometer", 19 | "ambient-light-sensor", 20 | "aria-notify", 21 | "attribution-reporting", 22 | "autoplay", 23 | "bluetooth", 24 | "browsing-topics", 25 | "camera", 26 | "captured-surface-control", 27 | "ch-device-memory", 28 | "ch-downlink", 29 | "ch-dpr", 30 | "ch-ect", 31 | "ch-prefers-color-scheme", 32 | "ch-prefers-reduced-motion", 33 | "ch-prefers-reduced-transparency", 34 | "ch-rtt", 35 | "ch-save-data", 36 | "ch-ua", 37 | "ch-ua-arch", 38 | "ch-ua-bitness", 39 | "ch-ua-form-factors", 40 | "ch-ua-full-version", 41 | "ch-ua-full-version-list", 42 | "ch-ua-mobile", 43 | "ch-ua-model", 44 | "ch-ua-platform", 45 | "ch-ua-platform-version", 46 | "ch-ua-wow64", 47 | "ch-viewport-height", 48 | "ch-viewport-width", 49 | "ch-width", 50 | "clipboard-read", 51 | "clipboard-write", 52 | "compute-pressure", 53 | "cross-origin-isolated", 54 | "deferred-fetch", 55 | "deferred-fetch-minimal", 56 | "device-attributes", 57 | "digital-credentials-get", 58 | "display-capture", 59 | "encrypted-media", 60 | "execution-while-not-rendered", 61 | "execution-while-out-of-viewport", 62 | "focus-without-user-activation", 63 | "fullscreen", 64 | "gamepad", 65 | "geolocation", 66 | "gyroscope", 67 | "hid", 68 | "identity-credentials-get", 69 | "idle-detection", 70 | "interest-cohort", 71 | "join-ad-interest-group", 72 | "keyboard-map", 73 | "language-detector", 74 | "language-model", 75 | "local-fonts", 76 | "local-network-access", 77 | "magnetometer", 78 | "microphone", 79 | "midi", 80 | "on-device-speech-recognition", 81 | "otp-credentials", 82 | "payment", 83 | "picture-in-picture", 84 | "popins", 85 | "private-aggregation", 86 | "private-state-token-issuance", 87 | "private-state-token-redemption", 88 | "publickey-credentials-create", 89 | "publickey-credentials-get", 90 | "rewriter", 91 | "run-ad-auction", 92 | "screen-wake-lock", 93 | "serial", 94 | "shared-storage", 95 | "shared-storage-select-url", 96 | "speaker-selection", 97 | "storage-access", 98 | "summarizer", 99 | "sync-xhr", 100 | "translator", 101 | "unload", 102 | "usb", 103 | "vertical-scroll", 104 | "window-management", 105 | "writer", 106 | "xr-spatial-tracking", 107 | # Firefox-only features. 108 | # Retrieved from Firefox document.featurePolicy.allowedFeatures() 109 | # with dom.security.featurePolicy.header.enabled preference set to true 110 | # in about:config (plus a restart): 111 | "document-domain", 112 | "vr", 113 | "web-share", 114 | } 115 | 116 | 117 | class PermissionsPolicyMiddleware: 118 | sync_capable = True 119 | async_capable = True 120 | 121 | def __init__( 122 | self, 123 | get_response: ( 124 | Callable[[HttpRequest], HttpResponseBase] 125 | | Callable[[HttpRequest], Awaitable[HttpResponseBase]] 126 | ), 127 | ) -> None: 128 | self.get_response = get_response 129 | self.async_mode = iscoroutinefunction(self.get_response) 130 | 131 | if self.async_mode: 132 | # Mark the class as async-capable, but do the actual switch 133 | # inside __call__ to avoid swapping out dunder methods 134 | markcoroutinefunction(self) 135 | 136 | self.header_value # noqa: B018 - Access at setup so ImproperlyConfigured can be raised 137 | receiver(setting_changed)(self.clear_header_value) 138 | 139 | def __call__( 140 | self, request: HttpRequest 141 | ) -> HttpResponseBase | Awaitable[HttpResponseBase]: 142 | if self.async_mode: 143 | return self.__acall__(request) 144 | response = self.get_response(request) 145 | assert isinstance(response, HttpResponseBase) # type narrow 146 | value = self.header_value 147 | if value: 148 | response["Permissions-Policy"] = value 149 | return response 150 | 151 | async def __acall__(self, request: HttpRequest) -> HttpResponseBase: 152 | result = self.get_response(request) 153 | assert not isinstance(result, HttpResponseBase) # type narrow 154 | response = await result 155 | value = self.header_value 156 | if value: # pragma: no branch 157 | response["Permissions-Policy"] = value 158 | return response 159 | 160 | @cached_property 161 | def header_value(self) -> str: 162 | setting: dict[str, str | list[str] | tuple[str]] = getattr( 163 | settings, "PERMISSIONS_POLICY", {} 164 | ) 165 | pieces = [] 166 | for feature, values in sorted(setting.items()): 167 | if feature not in _FEATURE_NAMES: 168 | raise ImproperlyConfigured(f"Unknown feature {feature}") 169 | if isinstance(values, str): 170 | values = (values,) 171 | 172 | item = [] 173 | for value in values: 174 | if value == "none": 175 | # 'none' was previously supported as a special token for 176 | # Feature-Policy, now can be represented by the empty list. 177 | pass 178 | elif value in ("self", "*"): 179 | item.append(value) 180 | else: 181 | item.append(f'"{value}"') # noqa: B028 182 | pieces.append(feature + "=(" + " ".join(item) + ")") 183 | return ", ".join(pieces) 184 | 185 | def clear_header_value(self, setting: str, **kwargs: object) -> None: 186 | if setting == "PERMISSIONS_POLICY": 187 | try: 188 | del self.header_value 189 | except AttributeError: 190 | pass 191 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | * Drop Python 3.9 support. 6 | 7 | 4.28.0 (2025-09-18) 8 | ------------------- 9 | 10 | * Support Django 6.0. 11 | 12 | 4.27.0 (2025-09-08) 13 | ------------------- 14 | 15 | * Support Python 3.14. 16 | 17 | * Updated to the latest set of features from Chrome 141 dev. 18 | 19 | New features: 20 | 21 | * ``aria-notify`` 22 | * ``language-model`` 23 | * ``local-network-access`` 24 | * ``on-device-speech-recognition`` 25 | 26 | Removed features: 27 | 28 | * ``web-app-installation`` 29 | 30 | 4.26.0 (2025-04-30) 31 | ------------------- 32 | 33 | * Updated to the latest set of features from Chrome 137 dev. 34 | 35 | New features: 36 | 37 | * ``device-attributes`` 38 | * ``language-detector`` 39 | * ``rewriter`` 40 | * ``summarizer`` 41 | * ``translator`` 42 | * ``writer`` 43 | 44 | 4.25.0 (2025-02-06) 45 | ------------------- 46 | 47 | * Support Django 5.2. 48 | 49 | 4.24.0 (2025-01-07) 50 | ------------------- 51 | 52 | * Updated to the latest set of features from Chrome 133 dev. 53 | 54 | New features: 55 | 56 | * ``deferred-fetch-minimal`` 57 | 58 | 4.23.0 (2024-10-29) 59 | ------------------- 60 | 61 | * Drop Django 3.2 to 4.1 support. 62 | 63 | 4.22.0 (2024-10-11) 64 | ------------------- 65 | 66 | * Updated to the latest set of features from Chrome 131 dev. 67 | 68 | New features: 69 | 70 | * ``popins`` 71 | * ``web-app-installation`` 72 | 73 | * Drop Python 3.8 support. 74 | 75 | * Support Python 3.13. 76 | 77 | 4.21.0 (2024-08-08) 78 | ------------------- 79 | 80 | * Updated to the latest set of features from Chrome 129 dev. 81 | 82 | New features: 83 | 84 | * ``captured-surface-control`` 85 | * ``ch-ua-form-factors`` (renamed from ``ch-ua-form-factor``) 86 | * ``deferred-fetch`` 87 | * ``digital-credentials-get`` 88 | 89 | Removed features: 90 | 91 | * ``ch-ua-form-factor`` 92 | * ``direct-sockets`` 93 | * ``usb-unrestricted`` 94 | 95 | 4.20.0 (2024-06-19) 96 | ------------------- 97 | 98 | * Support Django 5.1. 99 | 100 | 4.19.0 (2024-01-21) 101 | ------------------- 102 | 103 | * Fix ASGI compatibility on Python 3.12. 104 | 105 | Thanks to Alexandre Spaeth in `PR #426 `__. 106 | 107 | * Updated to the latest set of features from Chrome 122 dev. 108 | 109 | New features: 110 | 111 | - ``publickey-credentials-create`` 112 | - ``usb-unrestricted`` 113 | 114 | 4.18.0 (2023-10-09) 115 | ------------------- 116 | 117 | * Support Django 5.0. 118 | 119 | * Updated to the latest set of features from Chrome 119 dev. 120 | 121 | New features: 122 | 123 | * ``browsing-topics`` 124 | * ``ch-prefers-reduced-transparency`` 125 | * ``ch-ua-form-factor`` 126 | * ``interest-cohort`` 127 | * ``join-ad-interest-group`` 128 | * ``private-aggregation`` 129 | * ``private-state-token-issuance`` 130 | * ``private-state-token-redemption`` 131 | * ``run-ad-auction`` 132 | * ``shared-storage`` 133 | * ``shared-storage-select-url`` 134 | * ``window-management`` 135 | 136 | 4.17.0 (2023-07-10) 137 | ------------------- 138 | 139 | * Drop Python 3.7 support. 140 | 141 | 4.16.0 (2023-06-14) 142 | ------------------- 143 | 144 | * Support Python 3.12. 145 | 146 | 4.15.0 (2023-02-25) 147 | ------------------- 148 | 149 | * Support Django 4.2. 150 | 151 | * Updated to the latest set of features from Chrome 111 dev. 152 | 153 | New features: 154 | 155 | - ``identity-credentials-get`` 156 | - ``storage-access`` 157 | 158 | 4.14.0 (2022-11-30) 159 | ------------------- 160 | 161 | * Updated to the latest set of features from Chrome 109 dev. 162 | 163 | New features: 164 | 165 | - ``ch-prefers-reduced-motion`` 166 | - ``compute-pressure`` 167 | - ``direct-sockets`` 168 | - ``unload`` 169 | 170 | Removed features: 171 | 172 | - ``ch-partitioned-cookies`` 173 | 174 | 4.13.0 (2022-08-12) 175 | ------------------- 176 | 177 | * Add async support to the middleware, to reduce overhead on async views. 178 | 179 | 4.12.0 (2022-06-05) 180 | ------------------- 181 | 182 | * Support Python 3.11. 183 | 184 | * Support Django 4.1. 185 | 186 | 4.11.0 (2022-05-27) 187 | ------------------- 188 | 189 | * Updated to the latest set of features from Chrome 104 dev. 190 | 191 | New features: 192 | 193 | - ``bluetooth`` 194 | - ``ch-save-data`` 195 | - ``local-fonts`` 196 | 197 | * Restore ``interest-cohort`` feature and recommend disabling it in README. 198 | The original API, FLoC, was removed from Chrome, but there’s a replacement proposal `The Topics API `__. 199 | The proposal states that the ``interest-cohort`` feature will be recognized to disable it. 200 | 201 | 4.10.0 (2022-05-10) 202 | ------------------- 203 | 204 | * Drop support for Django 2.2, 3.0, and 3.1. 205 | 206 | 4.9.0 (2022-03-25) 207 | ------------------ 208 | 209 | * Updated to the latest set of features from Chrome 100 dev. 210 | 211 | New features: 212 | 213 | - ``ch-partitioned-cookies`` 214 | 215 | 4.8.0 (2022-02-16) 216 | ------------------ 217 | 218 | * Updated to the latest set of features from Chrome 100 dev. 219 | 220 | New features: 221 | 222 | - ``ch-ua-wow64`` 223 | 224 | Removed features: 225 | 226 | - ``interest-cohort`` 227 | 228 | 4.7.0 (2022-01-10) 229 | ------------------ 230 | 231 | * Drop Python 3.6 support. 232 | 233 | 4.6.0 (2021-12-29) 234 | ------------------ 235 | 236 | * Updated to the latest set of features from Chrome 98 dev. 237 | 238 | New features: 239 | 240 | - ``ch-ua-full-version-list`` 241 | - ``keyboard-map`` 242 | 243 | 4.5.0 (2021-10-06) 244 | ------------------ 245 | 246 | * Updated to the latest set of features from Chrome 96 dev and Firefox 93. 247 | 248 | New features: 249 | 250 | - ``ch-viewport-height`` 251 | - ``speaker-selection`` 252 | 253 | Removed features: 254 | 255 | - ``ch-lang`` 256 | - ``ch-ua-reduced`` 257 | - ``shared-autofill`` 258 | - ``speaker`` 259 | 260 | 4.4.0 (2021-10-05) 261 | ------------------ 262 | 263 | * Support Python 3.10. 264 | 265 | 4.3.0 (2021-09-28) 266 | ------------------ 267 | 268 | * Support Django 4.0. 269 | 270 | 4.2.0 (2021-08-07) 271 | ------------------ 272 | 273 | * Updated to the latest set of features from Chrome 94 dev. 274 | 275 | New features: 276 | 277 | - ``ch-ua-bitness`` 278 | - ``ch-ua-reduced`` 279 | 280 | * Add type hints. 281 | 282 | 4.1.0 (2021-06-02) 283 | ------------------ 284 | 285 | * Updated to the latest set of features from Chrome 92 dev. 286 | 287 | New features: 288 | 289 | - ``attribution-reporting`` 290 | - ``ch-prefers-color-scheme`` 291 | - ``shared-autofill`` 292 | - ``window-placement`` 293 | 294 | Removed features: 295 | 296 | - ``conversion-tracking`` 297 | 298 | 4.0.1 (2021-05-02) 299 | ------------------ 300 | 301 | * Improve setup instructions. 302 | 303 | 4.0.0 (2021-03-24) 304 | ------------------ 305 | 306 | * Rename the package from ``django-feature-policy`` to 307 | ``django-permissions-policy`` and the module name from 308 | ``django_feature_policy`` to ``django_permissions_policy`` accordingly. 309 | 310 | * Stop sending the ``Feature-Policy`` header. Chrome now logs warnings if it is 311 | sent alongside ``Permissions-Policy``. 312 | 313 | * Remove support for the legacy setting name ``FEATURE_POLICY`` and the old 314 | middleware alias ``FeaturePolicyMiddleware``. 315 | 316 | * Stop distributing tests to reduce package size. Tests are not intended to be 317 | run outside of the tox setup in the repository. Repackagers can use GitHub's 318 | tarballs per tag. 319 | 320 | 3.8.0 (2021-03-13) 321 | ------------------ 322 | 323 | * Updated to the latest set of features from Chrome 91 dev. 324 | 325 | New features: 326 | 327 | - ``conversion-measurement`` 328 | - ``interest-cohort`` 329 | - ``otp-credentials`` 330 | 331 | Removed features: 332 | 333 | - ``document-write`` 334 | - ``downloads`` 335 | - ``forms`` 336 | - ``modals`` 337 | - ``orientation-lock`` 338 | - ``pointer-lock`` 339 | - ``popups`` 340 | - ``presentation`` 341 | - ``scripts`` 342 | - ``sync-script`` 343 | - ``top-navigation`` 344 | 345 | 3.7.0 (2021-01-25) 346 | ------------------ 347 | 348 | * Support Django 3.2. 349 | 350 | 3.6.0 (2020-12-13) 351 | ------------------ 352 | 353 | * Drop Python 3.5 support. 354 | * Support Python 3.9. 355 | 356 | 3.5.0 (2020-10-11) 357 | ------------------ 358 | 359 | * Drop Django 2.0 and 2.1 support. 360 | * Move license from ISC to MIT License. 361 | * Update for the rename of the header from ``Feature-Policy`` to 362 | ``Permissions-Policy``. This means the middleware has been renamed to 363 | ``PermissionsPolicyMiddleware`` and the setting has been renamed to 364 | ``PERMISSIONS_POLICY``. The old names are supported as aliases for backwards 365 | compatibility. The middleware also sets both the old and new names for 366 | compatibility with older browsers. 367 | * Updated to the latest set of features from Chrome 86. 368 | 369 | New features: 370 | 371 | - ``ch-ua-platform-version`` 372 | - ``clipboard-read`` 373 | - ``clipboard-write`` 374 | - ``cross-origin-isolated`` 375 | - ``gamepad`` 376 | - ``publickey-credentials-get`` 377 | 378 | Removed features: 379 | 380 | - ``layout-animations`` 381 | - ``lazyload`` 382 | - ``loading-frame-default-eager`` 383 | * Added features from Firefox 81. This adds some unique features, and restores 384 | some features that Chrome has removed. 385 | 386 | New features: 387 | 388 | - ``display-capture`` 389 | - ``web-share`` 390 | 391 | Restored features: 392 | 393 | - ``speaker`` 394 | - ``vr`` 395 | 396 | 3.4.0 (2020-05-24) 397 | ------------------ 398 | 399 | * Updated to the latest set of features from Chrome 83. 400 | 401 | New features: 402 | 403 | - ``ch-ua-full-version`` 404 | - ``screen-wake-lock`` 405 | 406 | Removed features: 407 | 408 | - ``font-display-late-swap`` 409 | - ``oversized-images`` 410 | - ``unoptimized-lossless-images`` 411 | - ``unoptimized-lossless-images-strict`` 412 | - ``unoptimized-lossy-images`` 413 | - ``unsized-media`` 414 | - ``wake-lock`` 415 | 416 | * Added Django 3.1 support. 417 | 418 | 3.3.0 (2020-04-09) 419 | ------------------ 420 | 421 | * Dropped Django 1.11 support. Only Django 2.0+ is supported now. 422 | * Updated to the latest set of features from Chrome 81. This adds 423 | 'ch-ua-mobile', removes 'document-access', and 'vr', and renames 424 | 'downloads-without-user-activation' to 'downloads'. 425 | 426 | 3.2.0 (2020-01-19) 427 | ------------------ 428 | 429 | * Updated to the latest set of features from Chrome. This adds 2 new features: 430 | 'document-access' and 'xr-spatial-tracking'. This also removes the 'speaker' 431 | since it has now been 432 | `removed from the w3c specification `__. 433 | 434 | 3.1.0 (2019-11-15) 435 | ------------------ 436 | 437 | * Updated to the latest set of features from Chrome. This adds 17 new features: 438 | 'ch-device-memory', 'ch-downlink', 'ch-dpr', 'ch-ect', 'ch-lang', 'ch-rtt', 439 | 'ch-ua', 'ch-ua-arch', 'ch-ua-model', 'ch-ua-platform', 'ch-viewport-width', 440 | 'ch-width', 'execution-while-not-rendered', and 441 | 'execution-while-out-of-viewport'. Chrome has also removed support for 442 | 'speaker' but since this is still in the specification, it has been left. 443 | * Converted setuptools metadata to configuration file. This meant removing the 444 | ``__version__`` attribute from the package. If you want to inspect the 445 | installed version, use 446 | ``importlib.metadata.version("django-feature-policy")`` 447 | (`docs `__ / 448 | `backport `__). 449 | * Support Python 3.8. 450 | 451 | 3.0.0 (2019-08-02) 452 | ------------------ 453 | 454 | * Updated to the latest set of features from Chrome. This removes 455 | 'legacy-image-formats' and 'unoptimized-images', and adds 17 new features: 456 | 'downloads-without-user-activation', 'focus-without-user-activation', 457 | 'forms', 'hid', 'idle-detection', 'loading-frame-default-eager', 'modals', 458 | 'orientation-lock', 'pointer-lock', 'popups', 'presentation', 'scripts', 459 | 'serial', 'top-navigation', 'unoptimized-lossless-images', 460 | 'unoptimized-lossless-images-strict' and 'unoptimized-lossy-images'. Note 461 | that most of these are still experimental as can be seen on the [W3C feature 462 | list](https://github.com/w3c/webappsec-feature-policy/blob/master/features.md). 463 | 464 | * Stop marking the distributed wheel as universal. Python 2 was never supported 465 | so the wheel was never actually universal. 466 | 467 | 2.3.0 (2019-05-19) 468 | ------------------ 469 | 470 | * Update Python support to 3.5-3.7, as 3.4 has reached its end of life. 471 | 472 | * Make the generated header deterministic by iterating the settings dict in 473 | sorted order. 474 | 475 | * Support Django 1.11 for completeness. 476 | 477 | 2.2.0 (2019-05-08) 478 | ------------------ 479 | 480 | * Fix interpretation of '*' by not automatically adding quotes. 481 | * Optimize header generation to reduce impact on every request. 482 | 483 | 2.1.0 (2019-04-28) 484 | ------------------ 485 | 486 | * Tested on Django 2.2. No changes were needed for compatibility. 487 | 488 | 2.0.0 (2019-03-29) 489 | ------------------ 490 | 491 | * Updated to the latest set of features from Chrome. 492 | 'animations', 'image-compression', and 'max-downscaling-image' have been 493 | removed, whilst 'document-domain', 'font-display-late-swap', 494 | 'layout-animations', 'oversized-images', 'unoptimized-images', and 495 | 'wake-lock' have been added. 496 | See more at https://github.com/w3c/webappsec-feature-policy/blob/master/features.md . 497 | 498 | 1.0.1 (2019-01-02) 499 | ------------------ 500 | 501 | * Support for new 'lazyload' feature, per https://www.chromestatus.com/feature/5641405942726656. 502 | 503 | 1.0.0 (2018-10-24) 504 | ------------------ 505 | 506 | * First release, supporting adding the header with a middleware. 507 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 3 3 | requires-python = ">=3.10" 4 | resolution-markers = [ 5 | "python_full_version >= '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60'", 6 | "python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60'", 7 | "extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 8 | "extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 9 | "extra != 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 10 | "extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 11 | "python_full_version >= '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 12 | "python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 13 | ] 14 | conflicts = [[ 15 | { package = "django-permissions-policy", group = "django42" }, 16 | { package = "django-permissions-policy", group = "django50" }, 17 | { package = "django-permissions-policy", group = "django51" }, 18 | { package = "django-permissions-policy", group = "django52" }, 19 | { package = "django-permissions-policy", group = "django60" }, 20 | ]] 21 | 22 | [[package]] 23 | name = "asgiref" 24 | version = "3.11.0" 25 | source = { registry = "https://pypi.org/simple" } 26 | dependencies = [ 27 | { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 28 | ] 29 | sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } 30 | wheels = [ 31 | { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, 32 | ] 33 | 34 | [[package]] 35 | name = "colorama" 36 | version = "0.4.6" 37 | source = { registry = "https://pypi.org/simple" } 38 | 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" } 39 | wheels = [ 40 | { 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" }, 41 | ] 42 | 43 | [[package]] 44 | name = "coverage" 45 | version = "7.12.0" 46 | source = { registry = "https://pypi.org/simple" } 47 | sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } 48 | wheels = [ 49 | { url = "https://files.pythonhosted.org/packages/26/4a/0dc3de1c172d35abe512332cfdcc43211b6ebce629e4cc42e6cd25ed8f4d/coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b", size = 217409, upload-time = "2025-11-18T13:31:53.122Z" }, 50 | { url = "https://files.pythonhosted.org/packages/01/c3/086198b98db0109ad4f84241e8e9ea7e5fb2db8c8ffb787162d40c26cc76/coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c", size = 217927, upload-time = "2025-11-18T13:31:54.458Z" }, 51 | { url = "https://files.pythonhosted.org/packages/5d/5f/34614dbf5ce0420828fc6c6f915126a0fcb01e25d16cf141bf5361e6aea6/coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832", size = 244678, upload-time = "2025-11-18T13:31:55.805Z" }, 52 | { url = "https://files.pythonhosted.org/packages/55/7b/6b26fb32e8e4a6989ac1d40c4e132b14556131493b1d06bc0f2be169c357/coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa", size = 246507, upload-time = "2025-11-18T13:31:57.05Z" }, 53 | { url = "https://files.pythonhosted.org/packages/06/42/7d70e6603d3260199b90fb48b537ca29ac183d524a65cc31366b2e905fad/coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73", size = 248366, upload-time = "2025-11-18T13:31:58.362Z" }, 54 | { url = "https://files.pythonhosted.org/packages/2d/4a/d86b837923878424c72458c5b25e899a3c5ca73e663082a915f5b3c4d749/coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb", size = 245366, upload-time = "2025-11-18T13:31:59.572Z" }, 55 | { url = "https://files.pythonhosted.org/packages/e6/c2/2adec557e0aa9721875f06ced19730fdb7fc58e31b02b5aa56f2ebe4944d/coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e", size = 246408, upload-time = "2025-11-18T13:32:00.784Z" }, 56 | { url = "https://files.pythonhosted.org/packages/5a/4b/8bd1f1148260df11c618e535fdccd1e5aaf646e55b50759006a4f41d8a26/coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777", size = 244416, upload-time = "2025-11-18T13:32:01.963Z" }, 57 | { url = "https://files.pythonhosted.org/packages/0e/13/3a248dd6a83df90414c54a4e121fd081fb20602ca43955fbe1d60e2312a9/coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553", size = 244681, upload-time = "2025-11-18T13:32:03.408Z" }, 58 | { url = "https://files.pythonhosted.org/packages/76/30/aa833827465a5e8c938935f5d91ba055f70516941078a703740aaf1aa41f/coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d", size = 245300, upload-time = "2025-11-18T13:32:04.686Z" }, 59 | { url = "https://files.pythonhosted.org/packages/38/24/f85b3843af1370fb3739fa7571819b71243daa311289b31214fe3e8c9d68/coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef", size = 220008, upload-time = "2025-11-18T13:32:05.806Z" }, 60 | { url = "https://files.pythonhosted.org/packages/3a/a2/c7da5b9566f7164db9eefa133d17761ecb2c2fde9385d754e5b5c80f710d/coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022", size = 220943, upload-time = "2025-11-18T13:32:07.166Z" }, 61 | { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, 62 | { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, 63 | { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, 64 | { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, 65 | { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, 66 | { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, 67 | { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, 68 | { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, 69 | { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, 70 | { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, 71 | { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, 72 | { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, 73 | { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, 74 | { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, 75 | { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, 76 | { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, 77 | { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, 78 | { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, 79 | { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, 80 | { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, 81 | { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, 82 | { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, 83 | { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, 84 | { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, 85 | { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, 86 | { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, 87 | { url = "https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941", size = 217725, upload-time = "2025-11-18T13:32:49.22Z" }, 88 | { url = "https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a", size = 218098, upload-time = "2025-11-18T13:32:50.78Z" }, 89 | { url = "https://files.pythonhosted.org/packages/fc/9c/b846bbc774ff81091a12a10203e70562c91ae71badda00c5ae5b613527b1/coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d", size = 249093, upload-time = "2025-11-18T13:32:52.554Z" }, 90 | { url = "https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211", size = 251686, upload-time = "2025-11-18T13:32:54.862Z" }, 91 | { url = "https://files.pythonhosted.org/packages/cc/75/b095bd4b39d49c3be4bffbb3135fea18a99a431c52dd7513637c0762fecb/coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d", size = 252930, upload-time = "2025-11-18T13:32:56.417Z" }, 92 | { url = "https://files.pythonhosted.org/packages/6e/f3/466f63015c7c80550bead3093aacabf5380c1220a2a93c35d374cae8f762/coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c", size = 249296, upload-time = "2025-11-18T13:32:58.074Z" }, 93 | { url = "https://files.pythonhosted.org/packages/27/86/eba2209bf2b7e28c68698fc13437519a295b2d228ba9e0ec91673e09fa92/coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9", size = 251068, upload-time = "2025-11-18T13:32:59.646Z" }, 94 | { url = "https://files.pythonhosted.org/packages/ec/55/ca8ae7dbba962a3351f18940b359b94c6bafdd7757945fdc79ec9e452dc7/coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0", size = 249034, upload-time = "2025-11-18T13:33:01.481Z" }, 95 | { url = "https://files.pythonhosted.org/packages/7a/d7/39136149325cad92d420b023b5fd900dabdd1c3a0d1d5f148ef4a8cedef5/coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508", size = 248853, upload-time = "2025-11-18T13:33:02.935Z" }, 96 | { url = "https://files.pythonhosted.org/packages/fe/b6/76e1add8b87ef60e00643b0b7f8f7bb73d4bf5249a3be19ebefc5793dd25/coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc", size = 250619, upload-time = "2025-11-18T13:33:04.336Z" }, 97 | { url = "https://files.pythonhosted.org/packages/95/87/924c6dc64f9203f7a3c1832a6a0eee5a8335dbe5f1bdadcc278d6f1b4d74/coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8", size = 220261, upload-time = "2025-11-18T13:33:06.493Z" }, 98 | { url = "https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07", size = 221072, upload-time = "2025-11-18T13:33:07.926Z" }, 99 | { url = "https://files.pythonhosted.org/packages/70/49/5c9dc46205fef31b1b226a6e16513193715290584317fd4df91cdaf28b22/coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc", size = 219702, upload-time = "2025-11-18T13:33:09.631Z" }, 100 | { url = "https://files.pythonhosted.org/packages/9b/62/f87922641c7198667994dd472a91e1d9b829c95d6c29529ceb52132436ad/coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87", size = 218420, upload-time = "2025-11-18T13:33:11.153Z" }, 101 | { url = "https://files.pythonhosted.org/packages/85/dd/1cc13b2395ef15dbb27d7370a2509b4aee77890a464fb35d72d428f84871/coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6", size = 218773, upload-time = "2025-11-18T13:33:12.569Z" }, 102 | { url = "https://files.pythonhosted.org/packages/74/40/35773cc4bb1e9d4658d4fb669eb4195b3151bef3bbd6f866aba5cd5dac82/coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7", size = 260078, upload-time = "2025-11-18T13:33:14.037Z" }, 103 | { url = "https://files.pythonhosted.org/packages/ec/ee/231bb1a6ffc2905e396557585ebc6bdc559e7c66708376d245a1f1d330fc/coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560", size = 262144, upload-time = "2025-11-18T13:33:15.601Z" }, 104 | { url = "https://files.pythonhosted.org/packages/28/be/32f4aa9f3bf0b56f3971001b56508352c7753915345d45fab4296a986f01/coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12", size = 264574, upload-time = "2025-11-18T13:33:17.354Z" }, 105 | { url = "https://files.pythonhosted.org/packages/68/7c/00489fcbc2245d13ab12189b977e0cf06ff3351cb98bc6beba8bd68c5902/coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296", size = 259298, upload-time = "2025-11-18T13:33:18.958Z" }, 106 | { url = "https://files.pythonhosted.org/packages/96/b4/f0760d65d56c3bea95b449e02570d4abd2549dc784bf39a2d4721a2d8ceb/coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507", size = 262150, upload-time = "2025-11-18T13:33:20.644Z" }, 107 | { url = "https://files.pythonhosted.org/packages/c5/71/9a9314df00f9326d78c1e5a910f520d599205907432d90d1c1b7a97aa4b1/coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d", size = 259763, upload-time = "2025-11-18T13:33:22.189Z" }, 108 | { url = "https://files.pythonhosted.org/packages/10/34/01a0aceed13fbdf925876b9a15d50862eb8845454301fe3cdd1df08b2182/coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2", size = 258653, upload-time = "2025-11-18T13:33:24.239Z" }, 109 | { url = "https://files.pythonhosted.org/packages/8d/04/81d8fd64928acf1574bbb0181f66901c6c1c6279c8ccf5f84259d2c68ae9/coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455", size = 260856, upload-time = "2025-11-18T13:33:26.365Z" }, 110 | { url = "https://files.pythonhosted.org/packages/f2/76/fa2a37bfaeaf1f766a2d2360a25a5297d4fb567098112f6517475eee120b/coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d", size = 220936, upload-time = "2025-11-18T13:33:28.165Z" }, 111 | { url = "https://files.pythonhosted.org/packages/f9/52/60f64d932d555102611c366afb0eb434b34266b1d9266fc2fe18ab641c47/coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c", size = 222001, upload-time = "2025-11-18T13:33:29.656Z" }, 112 | { url = "https://files.pythonhosted.org/packages/77/df/c303164154a5a3aea7472bf323b7c857fed93b26618ed9fc5c2955566bb0/coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d", size = 220273, upload-time = "2025-11-18T13:33:31.415Z" }, 113 | { url = "https://files.pythonhosted.org/packages/bf/2e/fc12db0883478d6e12bbd62d481210f0c8daf036102aa11434a0c5755825/coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92", size = 217777, upload-time = "2025-11-18T13:33:32.86Z" }, 114 | { url = "https://files.pythonhosted.org/packages/1f/c1/ce3e525d223350c6ec16b9be8a057623f54226ef7f4c2fee361ebb6a02b8/coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360", size = 218100, upload-time = "2025-11-18T13:33:34.532Z" }, 115 | { url = "https://files.pythonhosted.org/packages/15/87/113757441504aee3808cb422990ed7c8bcc2d53a6779c66c5adef0942939/coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac", size = 249151, upload-time = "2025-11-18T13:33:36.135Z" }, 116 | { url = "https://files.pythonhosted.org/packages/d9/1d/9529d9bd44049b6b05bb319c03a3a7e4b0a8a802d28fa348ad407e10706d/coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d", size = 251667, upload-time = "2025-11-18T13:33:37.996Z" }, 117 | { url = "https://files.pythonhosted.org/packages/11/bb/567e751c41e9c03dc29d3ce74b8c89a1e3396313e34f255a2a2e8b9ebb56/coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c", size = 253003, upload-time = "2025-11-18T13:33:39.553Z" }, 118 | { url = "https://files.pythonhosted.org/packages/e4/b3/c2cce2d8526a02fb9e9ca14a263ca6fc074449b33a6afa4892838c903528/coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434", size = 249185, upload-time = "2025-11-18T13:33:42.086Z" }, 119 | { url = "https://files.pythonhosted.org/packages/0e/a7/967f93bb66e82c9113c66a8d0b65ecf72fc865adfba5a145f50c7af7e58d/coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc", size = 251025, upload-time = "2025-11-18T13:33:43.634Z" }, 120 | { url = "https://files.pythonhosted.org/packages/b9/b2/f2f6f56337bc1af465d5b2dc1ee7ee2141b8b9272f3bf6213fcbc309a836/coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc", size = 248979, upload-time = "2025-11-18T13:33:46.04Z" }, 121 | { url = "https://files.pythonhosted.org/packages/f4/7a/bf4209f45a4aec09d10a01a57313a46c0e0e8f4c55ff2965467d41a92036/coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e", size = 248800, upload-time = "2025-11-18T13:33:47.546Z" }, 122 | { url = "https://files.pythonhosted.org/packages/b8/b7/1e01b8696fb0521810f60c5bbebf699100d6754183e6cc0679bf2ed76531/coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17", size = 250460, upload-time = "2025-11-18T13:33:49.537Z" }, 123 | { url = "https://files.pythonhosted.org/packages/71/ae/84324fb9cb46c024760e706353d9b771a81b398d117d8c1fe010391c186f/coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933", size = 220533, upload-time = "2025-11-18T13:33:51.16Z" }, 124 | { url = "https://files.pythonhosted.org/packages/e2/71/1033629deb8460a8f97f83e6ac4ca3b93952e2b6f826056684df8275e015/coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe", size = 221348, upload-time = "2025-11-18T13:33:52.776Z" }, 125 | { url = "https://files.pythonhosted.org/packages/0a/5f/ac8107a902f623b0c251abdb749be282dc2ab61854a8a4fcf49e276fce2f/coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d", size = 219922, upload-time = "2025-11-18T13:33:54.316Z" }, 126 | { url = "https://files.pythonhosted.org/packages/79/6e/f27af2d4da367f16077d21ef6fe796c874408219fa6dd3f3efe7751bd910/coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d", size = 218511, upload-time = "2025-11-18T13:33:56.343Z" }, 127 | { url = "https://files.pythonhosted.org/packages/67/dd/65fd874aa460c30da78f9d259400d8e6a4ef457d61ab052fd248f0050558/coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03", size = 218771, upload-time = "2025-11-18T13:33:57.966Z" }, 128 | { url = "https://files.pythonhosted.org/packages/55/e0/7c6b71d327d8068cb79c05f8f45bf1b6145f7a0de23bbebe63578fe5240a/coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9", size = 260151, upload-time = "2025-11-18T13:33:59.597Z" }, 129 | { url = "https://files.pythonhosted.org/packages/49/ce/4697457d58285b7200de6b46d606ea71066c6e674571a946a6ea908fb588/coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6", size = 262257, upload-time = "2025-11-18T13:34:01.166Z" }, 130 | { url = "https://files.pythonhosted.org/packages/2f/33/acbc6e447aee4ceba88c15528dbe04a35fb4d67b59d393d2e0d6f1e242c1/coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339", size = 264671, upload-time = "2025-11-18T13:34:02.795Z" }, 131 | { url = "https://files.pythonhosted.org/packages/87/ec/e2822a795c1ed44d569980097be839c5e734d4c0c1119ef8e0a073496a30/coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e", size = 259231, upload-time = "2025-11-18T13:34:04.397Z" }, 132 | { url = "https://files.pythonhosted.org/packages/72/c5/a7ec5395bb4a49c9b7ad97e63f0c92f6bf4a9e006b1393555a02dae75f16/coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13", size = 262137, upload-time = "2025-11-18T13:34:06.068Z" }, 133 | { url = "https://files.pythonhosted.org/packages/67/0c/02c08858b764129f4ecb8e316684272972e60777ae986f3865b10940bdd6/coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f", size = 259745, upload-time = "2025-11-18T13:34:08.04Z" }, 134 | { url = "https://files.pythonhosted.org/packages/5a/04/4fd32b7084505f3829a8fe45c1a74a7a728cb251aaadbe3bec04abcef06d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1", size = 258570, upload-time = "2025-11-18T13:34:09.676Z" }, 135 | { url = "https://files.pythonhosted.org/packages/48/35/2365e37c90df4f5342c4fa202223744119fe31264ee2924f09f074ea9b6d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b", size = 260899, upload-time = "2025-11-18T13:34:11.259Z" }, 136 | { url = "https://files.pythonhosted.org/packages/05/56/26ab0464ca733fa325e8e71455c58c1c374ce30f7c04cebb88eabb037b18/coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a", size = 221313, upload-time = "2025-11-18T13:34:12.863Z" }, 137 | { url = "https://files.pythonhosted.org/packages/da/1c/017a3e1113ed34d998b27d2c6dba08a9e7cb97d362f0ec988fcd873dcf81/coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291", size = 222423, upload-time = "2025-11-18T13:34:15.14Z" }, 138 | { url = "https://files.pythonhosted.org/packages/4c/36/bcc504fdd5169301b52568802bb1b9cdde2e27a01d39fbb3b4b508ab7c2c/coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384", size = 220459, upload-time = "2025-11-18T13:34:17.222Z" }, 139 | { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, 140 | ] 141 | 142 | [package.optional-dependencies] 143 | toml = [ 144 | { name = "tomli", marker = "python_full_version <= '3.11' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 145 | ] 146 | 147 | [[package]] 148 | name = "django" 149 | version = "4.2.27" 150 | source = { registry = "https://pypi.org/simple" } 151 | dependencies = [ 152 | { name = "asgiref", marker = "extra == 'group-25-django-permissions-policy-django42' or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 153 | { name = "sqlparse", marker = "extra == 'group-25-django-permissions-policy-django42' or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 154 | { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-25-django-permissions-policy-django42') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 155 | ] 156 | sdist = { url = "https://files.pythonhosted.org/packages/ce/ff/6aa5a94b85837af893ca82227301ac6ddf4798afda86151fb2066d26ca0a/django-4.2.27.tar.gz", hash = "sha256:b865fbe0f4a3d1ee36594c5efa42b20db3c8bbb10dff0736face1c6e4bda5b92", size = 10432781, upload-time = "2025-12-02T14:01:49.006Z" } 157 | wheels = [ 158 | { url = "https://files.pythonhosted.org/packages/dd/f5/1a2319cc090870bfe8c62ef5ad881a6b73b5f4ce7330c5cf2cb4f9536b12/django-4.2.27-py3-none-any.whl", hash = "sha256:f393a394053713e7d213984555c5b7d3caeee78b2ccb729888a0774dff6c11a8", size = 7995090, upload-time = "2025-12-02T14:01:44.234Z" }, 159 | ] 160 | 161 | [[package]] 162 | name = "django" 163 | version = "5.0.14" 164 | source = { registry = "https://pypi.org/simple" } 165 | dependencies = [ 166 | { name = "asgiref", marker = "extra == 'group-25-django-permissions-policy-django50' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 167 | { name = "sqlparse", marker = "extra == 'group-25-django-permissions-policy-django50' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 168 | { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 169 | ] 170 | sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/cc0205045386b5be8eecb15a95f290383d103f0db5f7e34f93dcc340d5b0/Django-5.0.14.tar.gz", hash = "sha256:29019a5763dbd48da1720d687c3522ef40d1c61be6fb2fad27ed79e9f655bc11", size = 10644306, upload-time = "2025-04-02T11:24:41.396Z" } 171 | wheels = [ 172 | { url = "https://files.pythonhosted.org/packages/c0/93/eabde8789f41910845567ebbff5aacd52fd80e54c934ce15b83d5f552d2c/Django-5.0.14-py3-none-any.whl", hash = "sha256:e762bef8629ee704de215ebbd32062b84f4e56327eed412e5544f6f6eb1dfd74", size = 8185934, upload-time = "2025-04-02T11:24:36.888Z" }, 173 | ] 174 | 175 | [[package]] 176 | name = "django" 177 | version = "5.1.15" 178 | source = { registry = "https://pypi.org/simple" } 179 | dependencies = [ 180 | { name = "asgiref", marker = "extra == 'group-25-django-permissions-policy-django51' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 181 | { name = "sqlparse", marker = "extra == 'group-25-django-permissions-policy-django51' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 182 | { name = "tzdata", marker = "(sys_platform == 'win32' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 183 | ] 184 | sdist = { url = "https://files.pythonhosted.org/packages/10/45/1ac68964193cfcc0b0912a0f68025d5bdb54f71ba7b8716e85b959874bd0/django-5.1.15.tar.gz", hash = "sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947", size = 10719662, upload-time = "2025-12-02T14:01:31.931Z" } 185 | wheels = [ 186 | { url = "https://files.pythonhosted.org/packages/27/79/372e091f0eba4ddb8228245ccd1baaa140e9658711f5e3a0056e540b4c1e/django-5.1.15-py3-none-any.whl", hash = "sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432", size = 8260901, upload-time = "2025-12-02T14:01:27.352Z" }, 187 | ] 188 | 189 | [[package]] 190 | name = "django" 191 | version = "5.2.9" 192 | source = { registry = "https://pypi.org/simple" } 193 | resolution-markers = [ 194 | "python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60'", 195 | "extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 196 | "python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52' and extra != 'group-25-django-permissions-policy-django60'", 197 | ] 198 | dependencies = [ 199 | { name = "asgiref", marker = "(python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51') or extra == 'group-25-django-permissions-policy-django52' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 200 | { name = "sqlparse", marker = "(python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51') or extra == 'group-25-django-permissions-policy-django52' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 201 | { name = "tzdata", marker = "(python_full_version < '3.12' and sys_platform == 'win32' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51') or (sys_platform == 'win32' and extra == 'group-25-django-permissions-policy-django52') or (sys_platform != 'win32' and extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 202 | ] 203 | sdist = { url = "https://files.pythonhosted.org/packages/eb/1c/188ce85ee380f714b704283013434976df8d3a2df8e735221a02605b6794/django-5.2.9.tar.gz", hash = "sha256:16b5ccfc5e8c27e6c0561af551d2ea32852d7352c67d452ae3e76b4f6b2ca495", size = 10848762, upload-time = "2025-12-02T14:01:08.418Z" } 204 | wheels = [ 205 | { url = "https://files.pythonhosted.org/packages/17/b0/7f42bfc38b8f19b78546d47147e083ed06e12fc29c42da95655e0962c6c2/django-5.2.9-py3-none-any.whl", hash = "sha256:3a4ea88a70370557ab1930b332fd2887a9f48654261cdffda663fef5976bb00a", size = 8290652, upload-time = "2025-12-02T14:01:03.485Z" }, 206 | ] 207 | 208 | [[package]] 209 | name = "django" 210 | version = "6.0" 211 | source = { registry = "https://pypi.org/simple" } 212 | resolution-markers = [ 213 | "python_full_version >= '3.12'", 214 | ] 215 | dependencies = [ 216 | { name = "asgiref", marker = "(python_full_version >= '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 217 | { name = "sqlparse", marker = "(python_full_version >= '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 218 | { name = "tzdata", marker = "(python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 219 | ] 220 | sdist = { url = "https://files.pythonhosted.org/packages/15/75/19762bfc4ea556c303d9af8e36f0cd910ab17dff6c8774644314427a2120/django-6.0.tar.gz", hash = "sha256:7b0c1f50c0759bbe6331c6a39c89ae022a84672674aeda908784617ef47d8e26", size = 10932418, upload-time = "2025-12-03T16:26:21.878Z" } 221 | wheels = [ 222 | { url = "https://files.pythonhosted.org/packages/d7/ae/f19e24789a5ad852670d6885f5480f5e5895576945fcc01817dfd9bc002a/django-6.0-py3-none-any.whl", hash = "sha256:1cc2c7344303bbfb7ba5070487c17f7fc0b7174bbb0a38cebf03c675f5f19b6d", size = 8339181, upload-time = "2025-12-03T16:26:16.231Z" }, 223 | ] 224 | 225 | [[package]] 226 | name = "django-permissions-policy" 227 | version = "4.28.0" 228 | source = { editable = "." } 229 | dependencies = [ 230 | { name = "asgiref" }, 231 | { name = "django", version = "4.2.27", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-25-django-permissions-policy-django42' or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 232 | { name = "django", version = "5.0.14", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-25-django-permissions-policy-django50' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 233 | { name = "django", version = "5.1.15", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-25-django-permissions-policy-django51' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 234 | { name = "django", version = "5.2.9", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51') or extra == 'group-25-django-permissions-policy-django52' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60')" }, 235 | { name = "django", version = "6.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra != 'group-25-django-permissions-policy-django42' and extra != 'group-25-django-permissions-policy-django50' and extra != 'group-25-django-permissions-policy-django51' and extra != 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 236 | ] 237 | 238 | [package.dev-dependencies] 239 | django42 = [ 240 | { name = "django", version = "4.2.27", source = { registry = "https://pypi.org/simple" } }, 241 | ] 242 | django50 = [ 243 | { name = "django", version = "5.0.14", source = { registry = "https://pypi.org/simple" } }, 244 | ] 245 | django51 = [ 246 | { name = "django", version = "5.1.15", source = { registry = "https://pypi.org/simple" } }, 247 | ] 248 | django52 = [ 249 | { name = "django", version = "5.2.9", source = { registry = "https://pypi.org/simple" } }, 250 | ] 251 | django60 = [ 252 | { name = "django", version = "6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, 253 | ] 254 | test = [ 255 | { name = "coverage", extra = ["toml"] }, 256 | { name = "pytest" }, 257 | { name = "pytest-django" }, 258 | { name = "pytest-randomly" }, 259 | ] 260 | 261 | [package.metadata] 262 | requires-dist = [ 263 | { name = "asgiref", specifier = ">=3.6" }, 264 | { name = "django", specifier = ">=4.2" }, 265 | ] 266 | 267 | [package.metadata.requires-dev] 268 | django42 = [{ name = "django", marker = "python_full_version >= '3.8'", specifier = ">=4.2a1,<5" }] 269 | django50 = [{ name = "django", marker = "python_full_version >= '3.10'", specifier = ">=5a1,<5.1" }] 270 | django51 = [{ name = "django", marker = "python_full_version >= '3.10'", specifier = ">=5.1a1,<5.2" }] 271 | django52 = [{ name = "django", marker = "python_full_version >= '3.10'", specifier = ">=5.2a1,<6" }] 272 | django60 = [{ name = "django", marker = "python_full_version >= '3.12'", specifier = ">=6a1,<6.1" }] 273 | test = [ 274 | { name = "coverage", extras = ["toml"] }, 275 | { name = "pytest" }, 276 | { name = "pytest-django" }, 277 | { name = "pytest-randomly" }, 278 | ] 279 | 280 | [[package]] 281 | name = "exceptiongroup" 282 | version = "1.3.1" 283 | source = { registry = "https://pypi.org/simple" } 284 | dependencies = [ 285 | { name = "typing-extensions", marker = "python_full_version < '3.12' or (python_full_version == '3.12.*' and extra == 'group-25-django-permissions-policy-django42') or (python_full_version == '3.12.*' and extra == 'group-25-django-permissions-policy-django50') or (python_full_version == '3.12.*' and extra == 'group-25-django-permissions-policy-django51') or (python_full_version == '3.12.*' and extra == 'group-25-django-permissions-policy-django52') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (python_full_version >= '3.13' and extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 286 | ] 287 | sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } 288 | wheels = [ 289 | { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, 290 | ] 291 | 292 | [[package]] 293 | name = "iniconfig" 294 | version = "2.3.0" 295 | source = { registry = "https://pypi.org/simple" } 296 | 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" } 297 | wheels = [ 298 | { 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" }, 299 | ] 300 | 301 | [[package]] 302 | name = "packaging" 303 | version = "25.0" 304 | source = { registry = "https://pypi.org/simple" } 305 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 306 | wheels = [ 307 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 308 | ] 309 | 310 | [[package]] 311 | name = "pluggy" 312 | version = "1.6.0" 313 | source = { registry = "https://pypi.org/simple" } 314 | 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" } 315 | wheels = [ 316 | { 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" }, 317 | ] 318 | 319 | [[package]] 320 | name = "pygments" 321 | version = "2.19.2" 322 | source = { registry = "https://pypi.org/simple" } 323 | sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } 324 | wheels = [ 325 | { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, 326 | ] 327 | 328 | [[package]] 329 | name = "pytest" 330 | version = "9.0.2" 331 | source = { registry = "https://pypi.org/simple" } 332 | dependencies = [ 333 | { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 334 | { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 335 | { name = "iniconfig" }, 336 | { name = "packaging" }, 337 | { name = "pluggy" }, 338 | { name = "pygments" }, 339 | { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django50') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django42' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django51') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django50' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django52') or (extra == 'group-25-django-permissions-policy-django51' and extra == 'group-25-django-permissions-policy-django60') or (extra == 'group-25-django-permissions-policy-django52' and extra == 'group-25-django-permissions-policy-django60')" }, 340 | ] 341 | sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } 342 | wheels = [ 343 | { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, 344 | ] 345 | 346 | [[package]] 347 | name = "pytest-django" 348 | version = "4.11.1" 349 | source = { registry = "https://pypi.org/simple" } 350 | dependencies = [ 351 | { name = "pytest" }, 352 | ] 353 | sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } 354 | wheels = [ 355 | { url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, 356 | ] 357 | 358 | [[package]] 359 | name = "pytest-randomly" 360 | version = "4.0.1" 361 | source = { registry = "https://pypi.org/simple" } 362 | dependencies = [ 363 | { name = "pytest" }, 364 | ] 365 | sdist = { url = "https://files.pythonhosted.org/packages/c4/1d/258a4bf1109258c00c35043f40433be5c16647387b6e7cd5582d638c116b/pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8", size = 14130, upload-time = "2025-09-12T15:23:00.085Z" } 366 | wheels = [ 367 | { url = "https://files.pythonhosted.org/packages/33/3e/a4a9227807b56869790aad3e24472a554b585974fe7e551ea350f50897ae/pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7", size = 8304, upload-time = "2025-09-12T15:22:58.946Z" }, 368 | ] 369 | 370 | [[package]] 371 | name = "sqlparse" 372 | version = "0.5.4" 373 | source = { registry = "https://pypi.org/simple" } 374 | sdist = { url = "https://files.pythonhosted.org/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e", size = 120112, upload-time = "2025-11-28T07:10:18.377Z" } 375 | wheels = [ 376 | { url = "https://files.pythonhosted.org/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb", size = 45933, upload-time = "2025-11-28T07:10:19.73Z" }, 377 | ] 378 | 379 | [[package]] 380 | name = "tomli" 381 | version = "2.3.0" 382 | source = { registry = "https://pypi.org/simple" } 383 | sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } 384 | wheels = [ 385 | { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, 386 | { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, 387 | { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, 388 | { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, 389 | { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, 390 | { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, 391 | { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, 392 | { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, 393 | { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, 394 | { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, 395 | { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, 396 | { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, 397 | { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, 398 | { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, 399 | { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, 400 | { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, 401 | { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, 402 | { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, 403 | { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, 404 | { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, 405 | { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, 406 | { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, 407 | { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, 408 | { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, 409 | { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, 410 | { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, 411 | { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, 412 | { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, 413 | { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, 414 | { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, 415 | { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, 416 | { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, 417 | { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, 418 | { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, 419 | { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, 420 | { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, 421 | { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, 422 | { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, 423 | { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, 424 | { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, 425 | { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, 426 | ] 427 | 428 | [[package]] 429 | name = "typing-extensions" 430 | version = "4.15.0" 431 | source = { registry = "https://pypi.org/simple" } 432 | sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } 433 | wheels = [ 434 | { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, 435 | ] 436 | 437 | [[package]] 438 | name = "tzdata" 439 | version = "2025.2" 440 | source = { registry = "https://pypi.org/simple" } 441 | sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } 442 | wheels = [ 443 | { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, 444 | ] 445 | --------------------------------------------------------------------------------