Compare commits
73 Commits
Author | SHA1 | Date |
---|---|---|
|
32fdec1781 | |
|
9d0cbe8d4a | |
|
1802022994 | |
|
a3698902b5 | |
|
a5b3aa9c52 | |
|
5652c0c457 | |
|
00cab65315 | |
|
90a193d22c | |
|
288bd6bb34 | |
|
a04e52c022 | |
|
21ef53a156 | |
|
6da7890ac6 | |
|
fb47cbb2a5 | |
|
7783a8b6c7 | |
|
d21d9db90a | |
|
ac95c7a5b7 | |
|
4628c24f5c | |
|
a21413bd50 | |
|
347517a7cc | |
|
f95b27a25a | |
|
87e448593d | |
|
8dfa88cf8a | |
|
abb3137779 | |
|
c722cf0239 | |
|
7bb0f5e499 | |
|
1dd8b29493 | |
|
42fed6b200 | |
|
8aedfe81ef | |
|
1f169551e3 | |
|
f4f9a12081 | |
|
ad69f2c55f | |
|
e0de4b2faa | |
|
7fe752d8fd | |
|
d54d239a2d | |
|
798ac8ded0 | |
|
95be943d33 | |
|
4006df768c | |
|
3636a0d75f | |
|
e61b69bb50 | |
|
5a2825b00d | |
|
8acc883288 | |
|
7a30ef914b | |
|
1ae9fc2361 | |
|
0ebec538db | |
|
2be2c06569 | |
|
a1359112e9 | |
|
490cd06853 | |
|
9ced6bf2d1 | |
|
4eeab3b691 | |
|
95e87c71fc | |
|
c07d3d6467 | |
|
d69b7594a9 | |
|
d1eb3a08a8 | |
|
d15388b542 | |
|
5fede4d4f0 | |
|
0396592586 | |
|
9057c6b3df | |
|
547781fbd8 | |
|
40cbd82dda | |
|
9ce51ebff5 | |
|
0c1a388ca1 | |
|
a666227f55 | |
|
fe99f08e94 | |
|
2d1ba85c93 | |
|
613388ddde | |
|
a5cb27b678 | |
|
088409ea5c | |
|
11987280ba | |
|
95b33b39e6 | |
|
31afa6490f | |
|
f29c4506a6 | |
|
f907855966 | |
|
5ae8571ccd |
|
@ -21,32 +21,30 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "pip"
|
||||
allow-prereleases: true
|
||||
|
||||
- name: Install hatch
|
||||
run: pip install hatch
|
||||
- name: Install dependencies
|
||||
run: uv sync --frozen
|
||||
|
||||
- name: Test with pytest
|
||||
run: hatch run cov
|
||||
run: uv run cov --frozen
|
||||
|
||||
- name: Run E2E tests with behave
|
||||
run: hatch run e2e
|
||||
run: uv run e2e --frozen
|
||||
|
||||
- if: matrix.python-version == '3.13'
|
||||
name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
with:
|
||||
flags: unittests # optional
|
||||
name: coverage # optional
|
||||
|
@ -59,10 +57,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --frozen
|
||||
|
||||
- name: Run pre-commit
|
||||
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
||||
|
@ -75,15 +76,17 @@ jobs:
|
|||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
ignore-nothing-to-cache: true
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3
|
||||
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3
|
||||
with:
|
||||
languages: python
|
||||
config-file: ./.github/codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3
|
||||
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3
|
||||
|
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
environment: publish
|
||||
permissions:
|
||||
# IMPORTANT: this permission is mandatory for trusted publishing to pypi
|
||||
id-token: write
|
||||
|
@ -44,18 +44,16 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Upgrade pip
|
||||
run: pip install --upgrade pip
|
||||
|
||||
- name: Install hatch
|
||||
run: pip install hatch
|
||||
- name: Install dependencies
|
||||
run: uv sync --frozen
|
||||
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: hatch build
|
||||
run: uv build
|
||||
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
default_stages: [pre-commit]
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.6
|
||||
rev: v0.12.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-check
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
|
||||
|
@ -16,7 +16,7 @@ repos:
|
|||
- id: check-merge-conflict
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.15.0
|
||||
rev: v1.17.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: openfeature
|
||||
|
|
|
@ -1 +1 @@
|
|||
{".":"0.8.0"}
|
||||
{".":"0.8.2"}
|
109
CHANGELOG.md
109
CHANGELOG.md
|
@ -1,5 +1,114 @@
|
|||
# Changelog
|
||||
|
||||
## [0.8.2](https://github.com/open-feature/python-sdk/compare/v0.8.1...v0.8.2) (2025-07-30)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* merge transaction context into hook context evaluation context ([#521](https://github.com/open-feature/python-sdk/issues/521)) ([#523](https://github.com/open-feature/python-sdk/issues/523)) ([a5b3aa9](https://github.com/open-feature/python-sdk/commit/a5b3aa9c5213dda311068695f9209282f5faaff5))
|
||||
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
* starting migration to uv ([#512](https://github.com/open-feature/python-sdk/issues/512)) ([fb47cbb](https://github.com/open-feature/python-sdk/commit/fb47cbb2a51da9154adf977aad0b16575d227c33))
|
||||
|
||||
|
||||
### 🧹 Chore
|
||||
|
||||
* **deps:** pin astral-sh/setup-uv action to bd01e18 ([#514](https://github.com/open-feature/python-sdk/issues/514)) ([6da7890](https://github.com/open-feature/python-sdk/commit/6da7890ac6488bccf640f74bdc530fa9ce8bbec3))
|
||||
* **deps:** update actions/setup-python digest to a26af69 ([#489](https://github.com/open-feature/python-sdk/issues/489)) ([ad69f2c](https://github.com/open-feature/python-sdk/commit/ad69f2c55f3c8170a8a53981238130eb106207ba))
|
||||
* **deps:** update astral-sh/setup-uv digest to 7edac99 ([#524](https://github.com/open-feature/python-sdk/issues/524)) ([5652c0c](https://github.com/open-feature/python-sdk/commit/5652c0c457cc5a524e91405f3b229cf245ae4531))
|
||||
* **deps:** update codecov/codecov-action action to v5.4.2 ([#486](https://github.com/open-feature/python-sdk/issues/486)) ([798ac8d](https://github.com/open-feature/python-sdk/commit/798ac8ded00b8509068003367f36e6c04c574cbc))
|
||||
* **deps:** update codecov/codecov-action action to v5.4.3 ([#497](https://github.com/open-feature/python-sdk/issues/497)) ([7bb0f5e](https://github.com/open-feature/python-sdk/commit/7bb0f5e499ff8e0985b24696d0680251c90af32b))
|
||||
* **deps:** update github/codeql-action digest to 181d5ee ([#517](https://github.com/open-feature/python-sdk/issues/517)) ([a04e52c](https://github.com/open-feature/python-sdk/commit/a04e52c0224a6c1c269218df050ce7a56076211d))
|
||||
* **deps:** update github/codeql-action digest to 28deaed ([#488](https://github.com/open-feature/python-sdk/issues/488)) ([e0de4b2](https://github.com/open-feature/python-sdk/commit/e0de4b2faa109454a8079b934320f1c2b2b2b06e))
|
||||
* **deps:** update github/codeql-action digest to 39edc49 ([#515](https://github.com/open-feature/python-sdk/issues/515)) ([21ef53a](https://github.com/open-feature/python-sdk/commit/21ef53a156b17ce24db79c75b5bbfeaf2bd77f01))
|
||||
* **deps:** update github/codeql-action digest to 60168ef ([#492](https://github.com/open-feature/python-sdk/issues/492)) ([8aedfe8](https://github.com/open-feature/python-sdk/commit/8aedfe81ef67af3210ea9921e6b364fdd21ef8ac))
|
||||
* **deps:** update github/codeql-action digest to ce28f5b ([#508](https://github.com/open-feature/python-sdk/issues/508)) ([4628c24](https://github.com/open-feature/python-sdk/commit/4628c24f5c94821aecb06388703173bd5a8efc30))
|
||||
* **deps:** update github/codeql-action digest to fca7ace ([#505](https://github.com/open-feature/python-sdk/issues/505)) ([347517a](https://github.com/open-feature/python-sdk/commit/347517a7ccaf145a940fc6e2a37a8d1df621f3a3))
|
||||
* **deps:** update github/codeql-action digest to ff0a06e ([#498](https://github.com/open-feature/python-sdk/issues/498)) ([c722cf0](https://github.com/open-feature/python-sdk/commit/c722cf0239f2b9b95a1214b99447a9316c2c73d8))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.10 ([#496](https://github.com/open-feature/python-sdk/issues/496)) ([1dd8b29](https://github.com/open-feature/python-sdk/commit/1dd8b294930ee94e9c27a7bad4e43fda76bb1f9d))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.11 ([#499](https://github.com/open-feature/python-sdk/issues/499)) ([abb3137](https://github.com/open-feature/python-sdk/commit/abb31377790f95e4900e92f57050a4a12dc9f311))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.12 ([#501](https://github.com/open-feature/python-sdk/issues/501)) ([8dfa88c](https://github.com/open-feature/python-sdk/commit/8dfa88cf8aacada8b8b20803a51667b586e2182a))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.13 ([#507](https://github.com/open-feature/python-sdk/issues/507)) ([a21413b](https://github.com/open-feature/python-sdk/commit/a21413bd5069a9fd32378e197ae7709b34f001a5))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.5 ([#484](https://github.com/open-feature/python-sdk/issues/484)) ([95be943](https://github.com/open-feature/python-sdk/commit/95be943d33b9cfca137f94e5a1ad52bc562e4b4a))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.6 ([#487](https://github.com/open-feature/python-sdk/issues/487)) ([7fe752d](https://github.com/open-feature/python-sdk/commit/7fe752d8fd6148a38d53e5c7dfc071a6c8121c85))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.7 ([#490](https://github.com/open-feature/python-sdk/issues/490)) ([f4f9a12](https://github.com/open-feature/python-sdk/commit/f4f9a12081871f4797dc0e649219dc7be58d4116))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.8 ([#491](https://github.com/open-feature/python-sdk/issues/491)) ([1f16955](https://github.com/open-feature/python-sdk/commit/1f169551e37f286597400e5cb2c171a60a38436e))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.9 ([#493](https://github.com/open-feature/python-sdk/issues/493)) ([42fed6b](https://github.com/open-feature/python-sdk/commit/42fed6b2001b9056ed9bc7a390213206829a7b99))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.12.0 ([#510](https://github.com/open-feature/python-sdk/issues/510)) ([d21d9db](https://github.com/open-feature/python-sdk/commit/d21d9db90ae6f5f15f1aaf25a4e5d0669dbe1d96))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.12.1 ([#513](https://github.com/open-feature/python-sdk/issues/513)) ([7783a8b](https://github.com/open-feature/python-sdk/commit/7783a8b6c798246fc861fcb83e24427ea0f43e5f))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.12.2 ([#518](https://github.com/open-feature/python-sdk/issues/518)) ([288bd6b](https://github.com/open-feature/python-sdk/commit/288bd6bb34f2d5e857ae5b2b17f02f79276049c5))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.12.4 ([#525](https://github.com/open-feature/python-sdk/issues/525)) ([90a193d](https://github.com/open-feature/python-sdk/commit/90a193d22c64b8b708300dd436a16b8c7d632686))
|
||||
* **deps:** update pre-commit hook pre-commit/mirrors-mypy to v1.16.0 ([#503](https://github.com/open-feature/python-sdk/issues/503)) ([87e4485](https://github.com/open-feature/python-sdk/commit/87e448593d723b8239244a198e54e6cb056d3f95))
|
||||
* **deps:** update pre-commit hook pre-commit/mirrors-mypy to v1.16.1 ([#509](https://github.com/open-feature/python-sdk/issues/509)) ([ac95c7a](https://github.com/open-feature/python-sdk/commit/ac95c7a5b72d56e78dcb8e3411178967b00c04d9))
|
||||
* **deps:** update pre-commit hook pre-commit/mirrors-mypy to v1.17.0 ([#526](https://github.com/open-feature/python-sdk/issues/526)) ([00cab65](https://github.com/open-feature/python-sdk/commit/00cab65315ff3bc38f26b7281d8b258e6ec0f47d))
|
||||
* switch build backend to uv ([#527](https://github.com/open-feature/python-sdk/issues/527)) ([a369890](https://github.com/open-feature/python-sdk/commit/a3698902b55a1c9d53d4a7db2e6b18e4f2e77c70))
|
||||
* use publish env ([d54d239](https://github.com/open-feature/python-sdk/commit/d54d239a2d55359d9b49aa534a4b53339bf26571))
|
||||
|
||||
|
||||
### 🔄 Refactoring
|
||||
|
||||
* refine typing.Any type hints ([#504](https://github.com/open-feature/python-sdk/issues/504)) ([f95b27a](https://github.com/open-feature/python-sdk/commit/f95b27a25ae1dda7281c2039ec9060363de2703e))
|
||||
|
||||
## [0.8.1](https://github.com/open-feature/python-sdk/compare/v0.8.0...v0.8.1) (2025-04-09)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* add passthrough init to abstract provider ([#450](https://github.com/open-feature/python-sdk/issues/450)) ([088409e](https://github.com/open-feature/python-sdk/commit/088409ea5cdefef33f28fc4f45026fabac52377a))
|
||||
* fix cycle dependency between api and client ([#480](https://github.com/open-feature/python-sdk/issues/480)) ([3636a0d](https://github.com/open-feature/python-sdk/commit/3636a0d75f69712844a768cbc6c2f80fdcf6eb84))
|
||||
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
* add OTel utility function ([#451](https://github.com/open-feature/python-sdk/issues/451)) ([2d1ba85](https://github.com/open-feature/python-sdk/commit/2d1ba85c93cdd954f539d2872783b21683bd8b07))
|
||||
|
||||
|
||||
### 🧹 Chore
|
||||
|
||||
* add codeowner file to be consistent with the rest of openfeature ([#477](https://github.com/open-feature/python-sdk/issues/477)) ([7a30ef9](https://github.com/open-feature/python-sdk/commit/7a30ef914b3180fc72be9a1d2072a8a288e8b54d))
|
||||
* **deps:** update actions/setup-python digest to 8d9ed9a ([#473](https://github.com/open-feature/python-sdk/issues/473)) ([a135911](https://github.com/open-feature/python-sdk/commit/a1359112e9c1d740bcca501cbb5aadd9da3602b6))
|
||||
* **deps:** update codecov/codecov-action action to v5.4.0 ([#456](https://github.com/open-feature/python-sdk/issues/456)) ([a666227](https://github.com/open-feature/python-sdk/commit/a666227f55b14d4d2b6e43b6487ac643b6893739))
|
||||
* **deps:** update github/codeql-action digest to 1b549b9 ([#470](https://github.com/open-feature/python-sdk/issues/470)) ([4eeab3b](https://github.com/open-feature/python-sdk/commit/4eeab3b6914bd947a63f8d3c5bb89b85b7c2ced1))
|
||||
* **deps:** update github/codeql-action digest to 45775bd ([#483](https://github.com/open-feature/python-sdk/issues/483)) ([5a2825b](https://github.com/open-feature/python-sdk/commit/5a2825b00db0653c6d0496ec7f4703f9125cbed7))
|
||||
* **deps:** update github/codeql-action digest to 5f8171a ([#467](https://github.com/open-feature/python-sdk/issues/467)) ([d69b759](https://github.com/open-feature/python-sdk/commit/d69b7594a956a49385ef3030c212624d628aec74))
|
||||
* **deps:** update github/codeql-action digest to 6bb031a ([#462](https://github.com/open-feature/python-sdk/issues/462)) ([0396592](https://github.com/open-feature/python-sdk/commit/0396592586b6f721754c18a46b6d2fee3c2f80e8))
|
||||
* **deps:** update github/codeql-action digest to b56ba49 ([#454](https://github.com/open-feature/python-sdk/issues/454)) ([613388d](https://github.com/open-feature/python-sdk/commit/613388ddde33b6ce5ff3a39760970297dfa83255))
|
||||
* **deps:** update github/codeql-action digest to fc7e4a0 ([#481](https://github.com/open-feature/python-sdk/issues/481)) ([1ae9fc2](https://github.com/open-feature/python-sdk/commit/1ae9fc2361f1671cee8c794f02c01eb6ca0b77a6))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.10.0 ([#463](https://github.com/open-feature/python-sdk/issues/463)) ([5fede4d](https://github.com/open-feature/python-sdk/commit/5fede4d4f0cb6e39f84e85c72c9a2dd13434bc78))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.0 ([#465](https://github.com/open-feature/python-sdk/issues/465)) ([d1eb3a0](https://github.com/open-feature/python-sdk/commit/d1eb3a08a8da75022788cc4b9ea7b7d95aec4e69))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.1 ([#468](https://github.com/open-feature/python-sdk/issues/468)) ([c07d3d6](https://github.com/open-feature/python-sdk/commit/c07d3d64677c2ce475b098580b5eba1dd7f95a2e))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.2 ([#469](https://github.com/open-feature/python-sdk/issues/469)) ([95e87c7](https://github.com/open-feature/python-sdk/commit/95e87c71fc835cde7f7528e974509438ab8f2dc3))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.3 ([#475](https://github.com/open-feature/python-sdk/issues/475)) ([2be2c06](https://github.com/open-feature/python-sdk/commit/2be2c06569d89309a70793bb14a82be91d2ccf20))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.4 ([#476](https://github.com/open-feature/python-sdk/issues/476)) ([8acc883](https://github.com/open-feature/python-sdk/commit/8acc88328836c70f168ca87b71f4c49a6dba9381))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.9.10 ([#461](https://github.com/open-feature/python-sdk/issues/461)) ([9057c6b](https://github.com/open-feature/python-sdk/commit/9057c6b3df6ca5dc9e429db231eb4427cce031ea))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.9.7 ([#453](https://github.com/open-feature/python-sdk/issues/453)) ([a5cb27b](https://github.com/open-feature/python-sdk/commit/a5cb27b67839d60ea631001759478b2e74b75f28))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.9.8 ([#457](https://github.com/open-feature/python-sdk/issues/457)) ([0c1a388](https://github.com/open-feature/python-sdk/commit/0c1a388ca121e232f5c36b4b7a550d541ae34e5b))
|
||||
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.9.9 ([#458](https://github.com/open-feature/python-sdk/issues/458)) ([9ce51eb](https://github.com/open-feature/python-sdk/commit/9ce51ebff5a896b818e241fd8e3c2dea2fee610c))
|
||||
* **deps:** update spec digest to 09aef37 ([#460](https://github.com/open-feature/python-sdk/issues/460)) ([547781f](https://github.com/open-feature/python-sdk/commit/547781fbd82d1e2ee8a17988d11da3875d6a73dd))
|
||||
* **deps:** update spec digest to 0cd553d ([#455](https://github.com/open-feature/python-sdk/issues/455)) ([fe99f08](https://github.com/open-feature/python-sdk/commit/fe99f08e9465e8d35dd2b187d8ac01eae98432b7))
|
||||
* **deps:** update spec digest to 130df3e ([#471](https://github.com/open-feature/python-sdk/issues/471)) ([9ced6bf](https://github.com/open-feature/python-sdk/commit/9ced6bf2d1c7e3b0f01d062564ee63e49254af00))
|
||||
* **deps:** update spec digest to 25c57ee ([#459](https://github.com/open-feature/python-sdk/issues/459)) ([40cbd82](https://github.com/open-feature/python-sdk/commit/40cbd82dda20604a7a7be00e6913710d4a1ab56f))
|
||||
* **deps:** update spec digest to 27e4461 ([#472](https://github.com/open-feature/python-sdk/issues/472)) ([490cd06](https://github.com/open-feature/python-sdk/commit/490cd068533bb5ad702adf71915b6e0ac49706d8))
|
||||
* **deps:** update spec digest to 54952f3 ([#447](https://github.com/open-feature/python-sdk/issues/447)) ([f907855](https://github.com/open-feature/python-sdk/commit/f907855966cf788a3522e7626c76bd050de59a7e))
|
||||
* **deps:** update spec digest to a69f748 ([#452](https://github.com/open-feature/python-sdk/issues/452)) ([95b33b3](https://github.com/open-feature/python-sdk/commit/95b33b39e6ef472264002322162e83665054d71b))
|
||||
* **deps:** update spec digest to aad6193 ([#464](https://github.com/open-feature/python-sdk/issues/464)) ([d15388b](https://github.com/open-feature/python-sdk/commit/d15388b542798f7703578927dc5013863a83efa1))
|
||||
* improve resolve details callable type hints ([#449](https://github.com/open-feature/python-sdk/issues/449)) ([31afa64](https://github.com/open-feature/python-sdk/commit/31afa6490f7c2fc7a553b69c56840d494a520836))
|
||||
* revert spec to commit 0cd553d ([#479](https://github.com/open-feature/python-sdk/issues/479)) ([0ebec53](https://github.com/open-feature/python-sdk/commit/0ebec538db4d1180bad05e89bb62db23ca606a27))
|
||||
* use existing submodule version for e2e tests ([#444](https://github.com/open-feature/python-sdk/issues/444)) ([5ae8571](https://github.com/open-feature/python-sdk/commit/5ae8571ccd5f30c0aef87b0bc7f1a08a65254df0))
|
||||
* use keyword arguments, validate test ([#446](https://github.com/open-feature/python-sdk/issues/446)) ([f29c450](https://github.com/open-feature/python-sdk/commit/f29c4506a6a13307ba95a9b450a1b19c328975b3))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* fix linting issue on the readme ([1198728](https://github.com/open-feature/python-sdk/commit/11987280ba53ba087b1792316acc920a81434630))
|
||||
|
||||
|
||||
### 🔄 Refactoring
|
||||
|
||||
* replace exception raising with error flag resolution ([#474](https://github.com/open-feature/python-sdk/issues/474)) ([e61b69b](https://github.com/open-feature/python-sdk/commit/e61b69bb5079547c62a3ad51499326057db69e7a))
|
||||
|
||||
## [0.8.0](https://github.com/open-feature/python-sdk/compare/v0.7.5...v0.8.0) (2025-02-11)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
* @open-feature/sdk-python-maintainers @open-feature/maintainers
|
|
@ -12,19 +12,51 @@ Python 3.9 and above are supported by the SDK.
|
|||
|
||||
### Installation and Dependencies
|
||||
|
||||
We use [Hatch](https://hatch.pypa.io/) to manage the project.
|
||||
We use [uv](https://github.com/astral-sh/uv) for fast Python package management and dependency resolution.
|
||||
|
||||
To install Hatch, just run `pip install hatch`.
|
||||
To install uv, follow the [installation guide](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
|
||||
You will also need to set up the `pre-commit` hooks.
|
||||
Run `pre-commit install` in the root directory of the repository.
|
||||
If you don't have `pre-commit` installed, you can install it with `pip install pre-commit`.
|
||||
### Setup Development Environment
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://github.com/open-feature/python-sdk.git
|
||||
cd python-sdk
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
uv sync --frozen
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Run tests with `hatch run test`.
|
||||
Run tests:
|
||||
```bash
|
||||
uv run test --frozen
|
||||
```
|
||||
|
||||
### Coverage
|
||||
|
||||
Run tests with a coverage report:
|
||||
```bash
|
||||
uv run cov --frozen
|
||||
```
|
||||
|
||||
### End-to-End Tests
|
||||
|
||||
Run e2e tests with behave:
|
||||
```bash
|
||||
uv run e2e --frozen
|
||||
```
|
||||
|
||||
### Pre-commit
|
||||
|
||||
Run pre-commit hooks
|
||||
```bash
|
||||
uv run precommit --frozen
|
||||
```
|
||||
|
||||
We use `pytest` for our unit testing, making use of `parametrized` to inject cases at scale.
|
||||
|
||||
### Integration tests
|
||||
|
||||
|
@ -59,7 +91,7 @@ git remote add fork https://github.com/YOUR_GITHUB_USERNAME/python-sdk.git
|
|||
Ensure your development environment is all set up by building and testing
|
||||
|
||||
```bash
|
||||
hatch run test
|
||||
uv run test --frozen
|
||||
```
|
||||
|
||||
To start working on a new feature or bugfix, create a new branch and start working on it.
|
||||
|
|
13
README.md
13
README.md
|
@ -19,8 +19,8 @@
|
|||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
<a href="https://github.com/open-feature/python-sdk/releases/tag/v0.8.0">
|
||||
<img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.8.0&color=blue&style=for-the-badge" />
|
||||
<a href="https://github.com/open-feature/python-sdk/releases/tag/v0.8.2">
|
||||
<img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.8.2&color=blue&style=for-the-badge" />
|
||||
</a>
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
@ -60,13 +60,13 @@
|
|||
#### Pip install
|
||||
|
||||
```bash
|
||||
pip install openfeature-sdk==0.8.0
|
||||
pip install openfeature-sdk==0.8.2
|
||||
```
|
||||
|
||||
#### requirements.txt
|
||||
|
||||
```bash
|
||||
openfeature-sdk==0.8.0
|
||||
openfeature-sdk==0.8.2
|
||||
```
|
||||
|
||||
```python
|
||||
|
@ -411,8 +411,9 @@ class MyProvider(AbstractProvider):
|
|||
|
||||
Providers can also be extended to support async functionality.
|
||||
To support add asynchronous calls to a provider:
|
||||
* Implement the `AbstractProvider` as shown above.
|
||||
* Define asynchronous calls for each data type.
|
||||
|
||||
- Implement the `AbstractProvider` as shown above.
|
||||
- Define asynchronous calls for each data type.
|
||||
|
||||
```python
|
||||
class MyProvider(AbstractProvider):
|
||||
|
|
|
@ -61,7 +61,7 @@ def add_global_handler(event: ProviderEvent, handler: EventHandler) -> None:
|
|||
with _global_lock:
|
||||
_global_handlers[event].append(handler)
|
||||
|
||||
from openfeature.api import get_client
|
||||
from openfeature.api import get_client # noqa: PLC0415
|
||||
|
||||
_run_immediate_handler(get_client(), event, handler)
|
||||
|
||||
|
|
|
@ -2,19 +2,22 @@ import typing
|
|||
|
||||
from openfeature import _event_support
|
||||
from openfeature.client import OpenFeatureClient
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.evaluation_context import (
|
||||
get_evaluation_context,
|
||||
set_evaluation_context,
|
||||
)
|
||||
from openfeature.event import (
|
||||
EventHandler,
|
||||
ProviderEvent,
|
||||
)
|
||||
from openfeature.exception import GeneralError
|
||||
from openfeature.hook import Hook
|
||||
from openfeature.hook import add_hooks, clear_hooks, get_hooks
|
||||
from openfeature.provider import FeatureProvider
|
||||
from openfeature.provider._registry import provider_registry
|
||||
from openfeature.provider.metadata import Metadata
|
||||
from openfeature.transaction_context import TransactionContextPropagator
|
||||
from openfeature.transaction_context.no_op_transaction_context_propagator import (
|
||||
NoOpTransactionContextPropagator,
|
||||
from openfeature.transaction_context import (
|
||||
get_transaction_context,
|
||||
set_transaction_context,
|
||||
set_transaction_context_propagator,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
@ -35,13 +38,6 @@ __all__ = [
|
|||
"shutdown",
|
||||
]
|
||||
|
||||
_evaluation_context = EvaluationContext()
|
||||
_evaluation_transaction_context_propagator: TransactionContextPropagator = (
|
||||
NoOpTransactionContextPropagator()
|
||||
)
|
||||
|
||||
_hooks: list[Hook] = []
|
||||
|
||||
|
||||
def get_client(
|
||||
domain: typing.Optional[str] = None, version: typing.Optional[str] = None
|
||||
|
@ -67,49 +63,6 @@ def get_provider_metadata(domain: typing.Optional[str] = None) -> Metadata:
|
|||
return provider_registry.get_provider(domain).get_metadata()
|
||||
|
||||
|
||||
def get_evaluation_context() -> EvaluationContext:
|
||||
return _evaluation_context
|
||||
|
||||
|
||||
def set_evaluation_context(evaluation_context: EvaluationContext) -> None:
|
||||
global _evaluation_context
|
||||
if evaluation_context is None:
|
||||
raise GeneralError(error_message="No api level evaluation context")
|
||||
_evaluation_context = evaluation_context
|
||||
|
||||
|
||||
def set_transaction_context_propagator(
|
||||
transaction_context_propagator: TransactionContextPropagator,
|
||||
) -> None:
|
||||
global _evaluation_transaction_context_propagator
|
||||
_evaluation_transaction_context_propagator = transaction_context_propagator
|
||||
|
||||
|
||||
def get_transaction_context() -> EvaluationContext:
|
||||
return _evaluation_transaction_context_propagator.get_transaction_context()
|
||||
|
||||
|
||||
def set_transaction_context(evaluation_context: EvaluationContext) -> None:
|
||||
global _evaluation_transaction_context_propagator
|
||||
_evaluation_transaction_context_propagator.set_transaction_context(
|
||||
evaluation_context
|
||||
)
|
||||
|
||||
|
||||
def add_hooks(hooks: list[Hook]) -> None:
|
||||
global _hooks
|
||||
_hooks = _hooks + hooks
|
||||
|
||||
|
||||
def clear_hooks() -> None:
|
||||
global _hooks
|
||||
_hooks = []
|
||||
|
||||
|
||||
def get_hooks() -> list[Hook]:
|
||||
return _hooks
|
||||
|
||||
|
||||
def shutdown() -> None:
|
||||
provider_registry.shutdown()
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import logging
|
||||
import typing
|
||||
from collections.abc import Awaitable, Sequence
|
||||
from dataclasses import dataclass
|
||||
|
||||
from openfeature import _event_support, api
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature import _event_support
|
||||
from openfeature.evaluation_context import EvaluationContext, get_evaluation_context
|
||||
from openfeature.event import EventHandler, ProviderEvent
|
||||
from openfeature.exception import (
|
||||
ErrorCode,
|
||||
|
@ -18,9 +19,10 @@ from openfeature.flag_evaluation import (
|
|||
FlagEvaluationOptions,
|
||||
FlagResolutionDetails,
|
||||
FlagType,
|
||||
FlagValueType,
|
||||
Reason,
|
||||
)
|
||||
from openfeature.hook import Hook, HookContext, HookHints
|
||||
from openfeature.hook import Hook, HookContext, HookHints, get_hooks
|
||||
from openfeature.hook._hook_support import (
|
||||
after_all_hooks,
|
||||
after_hooks,
|
||||
|
@ -29,6 +31,7 @@ from openfeature.hook._hook_support import (
|
|||
)
|
||||
from openfeature.provider import FeatureProvider, ProviderStatus
|
||||
from openfeature.provider._registry import provider_registry
|
||||
from openfeature.transaction_context import get_transaction_context
|
||||
|
||||
__all__ = [
|
||||
"ClientMetadata",
|
||||
|
@ -37,46 +40,6 @@ __all__ = [
|
|||
|
||||
logger = logging.getLogger("openfeature")
|
||||
|
||||
GetDetailCallable = typing.Union[
|
||||
typing.Callable[
|
||||
[str, bool, typing.Optional[EvaluationContext]], FlagResolutionDetails[bool]
|
||||
],
|
||||
typing.Callable[
|
||||
[str, int, typing.Optional[EvaluationContext]], FlagResolutionDetails[int]
|
||||
],
|
||||
typing.Callable[
|
||||
[str, float, typing.Optional[EvaluationContext]], FlagResolutionDetails[float]
|
||||
],
|
||||
typing.Callable[
|
||||
[str, str, typing.Optional[EvaluationContext]], FlagResolutionDetails[str]
|
||||
],
|
||||
typing.Callable[
|
||||
[str, typing.Union[dict, list], typing.Optional[EvaluationContext]],
|
||||
FlagResolutionDetails[typing.Union[dict, list]],
|
||||
],
|
||||
]
|
||||
GetDetailCallableAsync = typing.Union[
|
||||
typing.Callable[
|
||||
[str, bool, typing.Optional[EvaluationContext]],
|
||||
typing.Awaitable[FlagResolutionDetails[bool]],
|
||||
],
|
||||
typing.Callable[
|
||||
[str, int, typing.Optional[EvaluationContext]],
|
||||
typing.Awaitable[FlagResolutionDetails[int]],
|
||||
],
|
||||
typing.Callable[
|
||||
[str, float, typing.Optional[EvaluationContext]],
|
||||
typing.Awaitable[FlagResolutionDetails[float]],
|
||||
],
|
||||
typing.Callable[
|
||||
[str, str, typing.Optional[EvaluationContext]],
|
||||
typing.Awaitable[FlagResolutionDetails[str]],
|
||||
],
|
||||
typing.Callable[
|
||||
[str, typing.Union[dict, list], typing.Optional[EvaluationContext]],
|
||||
typing.Awaitable[FlagResolutionDetails[typing.Union[dict, list]]],
|
||||
],
|
||||
]
|
||||
TypeMap = dict[
|
||||
FlagType,
|
||||
typing.Union[
|
||||
|
@ -88,6 +51,26 @@ TypeMap = dict[
|
|||
],
|
||||
]
|
||||
|
||||
T = typing.TypeVar("T", bool, int, float, str, typing.Union[dict, list])
|
||||
|
||||
|
||||
class ResolveDetailsCallable(typing.Protocol[T]):
|
||||
def __call__(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: T,
|
||||
evaluation_context: typing.Optional[EvaluationContext],
|
||||
) -> FlagResolutionDetails[T]: ...
|
||||
|
||||
|
||||
class ResolveDetailsCallableAsync(typing.Protocol[T]):
|
||||
def __call__(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: T,
|
||||
evaluation_context: typing.Optional[EvaluationContext],
|
||||
) -> Awaitable[FlagResolutionDetails[T]]: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClientMetadata:
|
||||
|
@ -360,10 +343,12 @@ class OpenFeatureClient:
|
|||
def get_object_value(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> typing.Union[dict, list]:
|
||||
) -> typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]:
|
||||
return self.get_object_details(
|
||||
flag_key,
|
||||
default_value,
|
||||
|
@ -374,10 +359,12 @@ class OpenFeatureClient:
|
|||
async def get_object_value_async(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> typing.Union[dict, list]:
|
||||
) -> typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]:
|
||||
details = await self.get_object_details_async(
|
||||
flag_key,
|
||||
default_value,
|
||||
|
@ -389,10 +376,14 @@ class OpenFeatureClient:
|
|||
def get_object_details(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[typing.Union[dict, list]]:
|
||||
) -> FlagEvaluationDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
return self.evaluate_flag_details(
|
||||
FlagType.OBJECT,
|
||||
flag_key,
|
||||
|
@ -404,10 +395,14 @@ class OpenFeatureClient:
|
|||
async def get_object_details_async(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[typing.Union[dict, list]]:
|
||||
) -> FlagEvaluationDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
return await self.evaluate_flag_details_async(
|
||||
FlagType.OBJECT,
|
||||
flag_key,
|
||||
|
@ -420,7 +415,7 @@ class OpenFeatureClient:
|
|||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Any,
|
||||
default_value: FlagValueType,
|
||||
evaluation_context: typing.Optional[EvaluationContext],
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions],
|
||||
) -> tuple[
|
||||
|
@ -440,11 +435,20 @@ class OpenFeatureClient:
|
|||
evaluation_hooks = flag_evaluation_options.hooks
|
||||
hook_hints = flag_evaluation_options.hook_hints
|
||||
|
||||
# Merge transaction context into evaluation context before creating hook_context
|
||||
# This ensures hooks have access to the complete context including transaction context
|
||||
merged_eval_context = (
|
||||
get_evaluation_context()
|
||||
.merge(get_transaction_context())
|
||||
.merge(self.context)
|
||||
.merge(evaluation_context)
|
||||
)
|
||||
|
||||
hook_context = HookContext(
|
||||
flag_key=flag_key,
|
||||
flag_type=flag_type,
|
||||
default_value=default_value,
|
||||
evaluation_context=evaluation_context,
|
||||
evaluation_context=merged_eval_context,
|
||||
client_metadata=self.get_metadata(),
|
||||
provider_metadata=provider.get_metadata(),
|
||||
)
|
||||
|
@ -452,10 +456,7 @@ class OpenFeatureClient:
|
|||
# in the flag evaluation
|
||||
# before: API, Client, Invocation, Provider
|
||||
merged_hooks = (
|
||||
api.get_hooks()
|
||||
+ self.hooks
|
||||
+ evaluation_hooks
|
||||
+ provider.get_provider_hooks()
|
||||
get_hooks() + self.hooks + evaluation_hooks + provider.get_provider_hooks()
|
||||
)
|
||||
# after, error, finally: Provider, Invocation, Client, API
|
||||
reversed_merged_hooks = merged_hooks[:]
|
||||
|
@ -465,15 +466,15 @@ class OpenFeatureClient:
|
|||
|
||||
def _assert_provider_status(
|
||||
self,
|
||||
) -> None:
|
||||
) -> typing.Optional[OpenFeatureError]:
|
||||
status = self.get_provider_status()
|
||||
if status == ProviderStatus.NOT_READY:
|
||||
raise ProviderNotReadyError()
|
||||
return ProviderNotReadyError()
|
||||
if status == ProviderStatus.FATAL:
|
||||
raise ProviderFatalError()
|
||||
return ProviderFatalError()
|
||||
return None
|
||||
|
||||
def _before_hooks_and_merge_context(
|
||||
def _run_before_hooks_and_update_context(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
hook_context: HookContext,
|
||||
|
@ -485,29 +486,84 @@ class OpenFeatureClient:
|
|||
# Any resulting evaluation context from a before hook will overwrite
|
||||
# duplicate fields defined globally, on the client, or in the invocation.
|
||||
# Requirement 3.2.2, 4.3.4: API.context->client.context->invocation.context
|
||||
invocation_context = before_hooks(
|
||||
before_hooks_context = before_hooks(
|
||||
flag_type, hook_context, merged_hooks, hook_hints
|
||||
)
|
||||
if evaluation_context:
|
||||
invocation_context = invocation_context.merge(ctx2=evaluation_context)
|
||||
|
||||
# Requirement 3.2.2 merge: API.context->transaction.context->client.context->invocation.context
|
||||
merged_context = (
|
||||
api.get_evaluation_context()
|
||||
.merge(api.get_transaction_context())
|
||||
.merge(self.context)
|
||||
.merge(invocation_context)
|
||||
)
|
||||
# The hook_context.evaluation_context already contains the merged context from
|
||||
# _establish_hooks_and_provider, so we just need to merge with the before hooks result
|
||||
merged_context = hook_context.evaluation_context.merge(before_hooks_context)
|
||||
|
||||
return merged_context
|
||||
|
||||
@typing.overload
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: bool,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[bool]: ...
|
||||
|
||||
@typing.overload
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: int,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[int]: ...
|
||||
|
||||
@typing.overload
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: float,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[float]: ...
|
||||
|
||||
@typing.overload
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: str,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[str]: ...
|
||||
|
||||
@typing.overload
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: Sequence["FlagValueType"],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[Sequence["FlagValueType"]]: ...
|
||||
|
||||
@typing.overload
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Mapping[str, "FlagValueType"],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[typing.Mapping[str, "FlagValueType"]]: ...
|
||||
|
||||
async def evaluate_flag_details_async(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Any,
|
||||
default_value: FlagValueType,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[typing.Any]:
|
||||
) -> FlagEvaluationDetails[FlagValueType]:
|
||||
"""
|
||||
Evaluate the flag requested by the user from the clients provider.
|
||||
|
||||
|
@ -530,9 +586,24 @@ class OpenFeatureClient:
|
|||
)
|
||||
|
||||
try:
|
||||
self._assert_provider_status()
|
||||
if provider_err := self._assert_provider_status():
|
||||
error_hooks(
|
||||
flag_type,
|
||||
hook_context,
|
||||
provider_err,
|
||||
reversed_merged_hooks,
|
||||
hook_hints,
|
||||
)
|
||||
flag_evaluation = FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=default_value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=provider_err.error_code,
|
||||
error_message=provider_err.error_message,
|
||||
)
|
||||
return flag_evaluation
|
||||
|
||||
merged_context = self._before_hooks_and_merge_context(
|
||||
merged_context = self._run_before_hooks_and_update_context(
|
||||
flag_type,
|
||||
hook_context,
|
||||
merged_hooks,
|
||||
|
@ -547,6 +618,11 @@ class OpenFeatureClient:
|
|||
default_value,
|
||||
merged_context,
|
||||
)
|
||||
if err := flag_evaluation.get_exception():
|
||||
error_hooks(
|
||||
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
|
||||
)
|
||||
return flag_evaluation
|
||||
|
||||
after_hooks(
|
||||
flag_type,
|
||||
|
@ -596,14 +672,74 @@ class OpenFeatureClient:
|
|||
hook_hints,
|
||||
)
|
||||
|
||||
@typing.overload
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Any,
|
||||
default_value: bool,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[typing.Any]:
|
||||
) -> FlagEvaluationDetails[bool]: ...
|
||||
|
||||
@typing.overload
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: int,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[int]: ...
|
||||
|
||||
@typing.overload
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: float,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[float]: ...
|
||||
|
||||
@typing.overload
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: str,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[str]: ...
|
||||
|
||||
@typing.overload
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: Sequence["FlagValueType"],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[Sequence["FlagValueType"]]: ...
|
||||
|
||||
@typing.overload
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Mapping[str, "FlagValueType"],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[typing.Mapping[str, "FlagValueType"]]: ...
|
||||
|
||||
def evaluate_flag_details(
|
||||
self,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: FlagValueType,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
|
||||
) -> FlagEvaluationDetails[FlagValueType]:
|
||||
"""
|
||||
Evaluate the flag requested by the user from the clients provider.
|
||||
|
||||
|
@ -626,9 +762,24 @@ class OpenFeatureClient:
|
|||
)
|
||||
|
||||
try:
|
||||
self._assert_provider_status()
|
||||
if provider_err := self._assert_provider_status():
|
||||
error_hooks(
|
||||
flag_type,
|
||||
hook_context,
|
||||
provider_err,
|
||||
reversed_merged_hooks,
|
||||
hook_hints,
|
||||
)
|
||||
flag_evaluation = FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=default_value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=provider_err.error_code,
|
||||
error_message=provider_err.error_message,
|
||||
)
|
||||
return flag_evaluation
|
||||
|
||||
merged_context = self._before_hooks_and_merge_context(
|
||||
merged_context = self._run_before_hooks_and_update_context(
|
||||
flag_type,
|
||||
hook_context,
|
||||
merged_hooks,
|
||||
|
@ -643,6 +794,12 @@ class OpenFeatureClient:
|
|||
default_value,
|
||||
merged_context,
|
||||
)
|
||||
if err := flag_evaluation.get_exception():
|
||||
error_hooks(
|
||||
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
|
||||
)
|
||||
flag_evaluation.value = default_value
|
||||
return flag_evaluation
|
||||
|
||||
after_hooks(
|
||||
flag_type,
|
||||
|
@ -698,16 +855,11 @@ class OpenFeatureClient:
|
|||
provider: FeatureProvider,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Any,
|
||||
default_value: FlagValueType,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagEvaluationDetails[typing.Any]:
|
||||
args = (
|
||||
flag_key,
|
||||
default_value,
|
||||
evaluation_context,
|
||||
)
|
||||
) -> FlagEvaluationDetails[FlagValueType]:
|
||||
get_details_callables_async: typing.Mapping[
|
||||
FlagType, GetDetailCallableAsync
|
||||
FlagType, ResolveDetailsCallableAsync
|
||||
] = {
|
||||
FlagType.BOOLEAN: provider.resolve_boolean_details_async,
|
||||
FlagType.INTEGER: provider.resolve_integer_details_async,
|
||||
|
@ -717,32 +869,42 @@ class OpenFeatureClient:
|
|||
}
|
||||
get_details_callable = get_details_callables_async.get(flag_type)
|
||||
if not get_details_callable:
|
||||
raise GeneralError(error_message="Unknown flag type")
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=default_value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=ErrorCode.GENERAL,
|
||||
error_message="Unknown flag type",
|
||||
)
|
||||
|
||||
resolution = await get_details_callable(*args)
|
||||
resolution.raise_for_error()
|
||||
resolution = await get_details_callable(
|
||||
flag_key=flag_key,
|
||||
default_value=default_value,
|
||||
evaluation_context=evaluation_context,
|
||||
)
|
||||
if resolution.error_code:
|
||||
return resolution.to_flag_evaluation_details(flag_key)
|
||||
|
||||
# we need to check the get_args to be compatible with union types.
|
||||
_typecheck_flag_value(resolution.value, flag_type)
|
||||
if err := _typecheck_flag_value(value=resolution.value, flag_type=flag_type):
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=resolution.value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=err.error_code,
|
||||
error_message=err.error_message,
|
||||
)
|
||||
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=resolution.value,
|
||||
variant=resolution.variant,
|
||||
flag_metadata=resolution.flag_metadata or {},
|
||||
reason=resolution.reason,
|
||||
error_code=resolution.error_code,
|
||||
error_message=resolution.error_message,
|
||||
)
|
||||
return resolution.to_flag_evaluation_details(flag_key)
|
||||
|
||||
def _create_provider_evaluation(
|
||||
self,
|
||||
provider: FeatureProvider,
|
||||
flag_type: FlagType,
|
||||
flag_key: str,
|
||||
default_value: typing.Any,
|
||||
default_value: FlagValueType,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagEvaluationDetails[typing.Any]:
|
||||
) -> FlagEvaluationDetails[FlagValueType]:
|
||||
"""
|
||||
Encapsulated method to create a FlagEvaluationDetail from a specific provider.
|
||||
|
||||
|
@ -753,13 +915,7 @@ class OpenFeatureClient:
|
|||
:return: a FlagEvaluationDetails object with the fully evaluated flag from a
|
||||
provider
|
||||
"""
|
||||
args = (
|
||||
flag_key,
|
||||
default_value,
|
||||
evaluation_context,
|
||||
)
|
||||
|
||||
get_details_callables: typing.Mapping[FlagType, GetDetailCallable] = {
|
||||
get_details_callables: typing.Mapping[FlagType, ResolveDetailsCallable] = {
|
||||
FlagType.BOOLEAN: provider.resolve_boolean_details,
|
||||
FlagType.INTEGER: provider.resolve_integer_details,
|
||||
FlagType.FLOAT: provider.resolve_float_details,
|
||||
|
@ -769,23 +925,33 @@ class OpenFeatureClient:
|
|||
|
||||
get_details_callable = get_details_callables.get(flag_type)
|
||||
if not get_details_callable:
|
||||
raise GeneralError(error_message="Unknown flag type")
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=default_value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=ErrorCode.GENERAL,
|
||||
error_message="Unknown flag type",
|
||||
)
|
||||
|
||||
resolution = get_details_callable(*args)
|
||||
resolution.raise_for_error()
|
||||
resolution = get_details_callable(
|
||||
flag_key=flag_key,
|
||||
default_value=default_value,
|
||||
evaluation_context=evaluation_context,
|
||||
)
|
||||
if resolution.error_code:
|
||||
return resolution.to_flag_evaluation_details(flag_key)
|
||||
|
||||
# we need to check the get_args to be compatible with union types.
|
||||
_typecheck_flag_value(resolution.value, flag_type)
|
||||
if err := _typecheck_flag_value(value=resolution.value, flag_type=flag_type):
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=resolution.value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=err.error_code,
|
||||
error_message=err.error_message,
|
||||
)
|
||||
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=resolution.value,
|
||||
variant=resolution.variant,
|
||||
flag_metadata=resolution.flag_metadata or {},
|
||||
reason=resolution.reason,
|
||||
error_code=resolution.error_code,
|
||||
error_message=resolution.error_message,
|
||||
)
|
||||
return resolution.to_flag_evaluation_details(flag_key)
|
||||
|
||||
def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
|
||||
_event_support.add_client_handler(self, event, handler)
|
||||
|
@ -794,7 +960,9 @@ class OpenFeatureClient:
|
|||
_event_support.remove_client_handler(self, event, handler)
|
||||
|
||||
|
||||
def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
|
||||
def _typecheck_flag_value(
|
||||
value: typing.Any, flag_type: FlagType
|
||||
) -> typing.Optional[OpenFeatureError]:
|
||||
type_map: TypeMap = {
|
||||
FlagType.BOOLEAN: bool,
|
||||
FlagType.STRING: str,
|
||||
|
@ -804,6 +972,7 @@ def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
|
|||
}
|
||||
_type = type_map.get(flag_type)
|
||||
if not _type:
|
||||
raise GeneralError(error_message="Unknown flag type")
|
||||
return GeneralError(error_message="Unknown flag type")
|
||||
if not isinstance(value, _type):
|
||||
raise TypeMismatchError(f"Expected type {_type} but got {type(value)}")
|
||||
return TypeMismatchError(f"Expected type {_type} but got {type(value)}")
|
||||
return None
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import typing
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
__all__ = ["EvaluationContext"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvaluationContext:
|
||||
targeting_key: typing.Optional[str] = None
|
||||
attributes: dict = field(default_factory=dict)
|
||||
|
||||
def merge(self, ctx2: "EvaluationContext") -> "EvaluationContext":
|
||||
if not (self and ctx2):
|
||||
return self or ctx2
|
||||
|
||||
attributes = {**self.attributes, **ctx2.attributes}
|
||||
targeting_key = ctx2.targeting_key or self.targeting_key
|
||||
|
||||
return EvaluationContext(targeting_key=targeting_key, attributes=attributes)
|
|
@ -0,0 +1,54 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
from openfeature.exception import GeneralError
|
||||
|
||||
__all__ = ["EvaluationContext", "get_evaluation_context", "set_evaluation_context"]
|
||||
|
||||
# https://openfeature.dev/specification/sections/evaluation-context#requirement-312
|
||||
EvaluationContextAttributes = typing.Mapping[
|
||||
str,
|
||||
typing.Union[
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
str,
|
||||
datetime,
|
||||
Sequence["EvaluationContextAttributes"],
|
||||
typing.Mapping[str, "EvaluationContextAttributes"],
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvaluationContext:
|
||||
targeting_key: typing.Optional[str] = None
|
||||
attributes: EvaluationContextAttributes = field(default_factory=dict)
|
||||
|
||||
def merge(self, ctx2: EvaluationContext) -> EvaluationContext:
|
||||
if not (self and ctx2):
|
||||
return self or ctx2
|
||||
|
||||
attributes = {**self.attributes, **ctx2.attributes}
|
||||
targeting_key = ctx2.targeting_key or self.targeting_key
|
||||
|
||||
return EvaluationContext(targeting_key=targeting_key, attributes=attributes)
|
||||
|
||||
|
||||
def get_evaluation_context() -> EvaluationContext:
|
||||
return _evaluation_context
|
||||
|
||||
|
||||
def set_evaluation_context(evaluation_context: EvaluationContext) -> None:
|
||||
global _evaluation_context
|
||||
if evaluation_context is None:
|
||||
raise GeneralError(error_message="No api level evaluation context")
|
||||
_evaluation_context = evaluation_context
|
||||
|
||||
|
||||
# need to be at the bottom, because of the definition order
|
||||
_evaluation_context = EvaluationContext()
|
|
@ -2,7 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import typing
|
||||
from collections.abc import Mapping
|
||||
from enum import Enum
|
||||
|
||||
from openfeature._backports.strenum import StrEnum
|
||||
|
||||
__all__ = [
|
||||
"ErrorCode",
|
||||
|
@ -163,7 +164,7 @@ class InvalidContextError(OpenFeatureError):
|
|||
super().__init__(ErrorCode.INVALID_CONTEXT, error_message)
|
||||
|
||||
|
||||
class ErrorCode(Enum):
|
||||
class ErrorCode(StrEnum):
|
||||
PROVIDER_NOT_READY = "PROVIDER_NOT_READY"
|
||||
PROVIDER_FATAL = "PROVIDER_FATAL"
|
||||
FLAG_NOT_FOUND = "FLAG_NOT_FOUND"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from openfeature._backports.strenum import StrEnum
|
||||
from openfeature.exception import ErrorCode
|
||||
from openfeature.exception import ErrorCode, OpenFeatureError
|
||||
|
||||
if typing.TYPE_CHECKING: # pragma: no cover
|
||||
# resolves a circular dependency in type annotations
|
||||
|
@ -34,13 +35,22 @@ class Reason(StrEnum):
|
|||
DEFAULT = "DEFAULT"
|
||||
DISABLED = "DISABLED"
|
||||
ERROR = "ERROR"
|
||||
STATIC = "STATIC"
|
||||
SPLIT = "SPLIT"
|
||||
STATIC = "STATIC"
|
||||
STALE = "STALE"
|
||||
TARGETING_MATCH = "TARGETING_MATCH"
|
||||
UNKNOWN = "UNKNOWN"
|
||||
|
||||
|
||||
FlagMetadata = typing.Mapping[str, typing.Any]
|
||||
FlagMetadata = typing.Mapping[str, typing.Union[bool, int, float, str]]
|
||||
FlagValueType = typing.Union[
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
str,
|
||||
Sequence["FlagValueType"],
|
||||
typing.Mapping[str, "FlagValueType"],
|
||||
]
|
||||
|
||||
T_co = typing.TypeVar("T_co", covariant=True)
|
||||
|
||||
|
@ -55,6 +65,11 @@ class FlagEvaluationDetails(typing.Generic[T_co]):
|
|||
error_code: typing.Optional[ErrorCode] = None
|
||||
error_message: typing.Optional[str] = None
|
||||
|
||||
def get_exception(self) -> typing.Optional[OpenFeatureError]:
|
||||
if self.error_code:
|
||||
return ErrorCode.to_exception(self.error_code, self.error_message or "")
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlagEvaluationOptions:
|
||||
|
@ -78,3 +93,14 @@ class FlagResolutionDetails(typing.Generic[U_co]):
|
|||
if self.error_code:
|
||||
raise ErrorCode.to_exception(self.error_code, self.error_message or "")
|
||||
return None
|
||||
|
||||
def to_flag_evaluation_details(self, flag_key: str) -> FlagEvaluationDetails[U_co]:
|
||||
return FlagEvaluationDetails(
|
||||
flag_key=flag_key,
|
||||
value=self.value,
|
||||
variant=self.variant,
|
||||
flag_metadata=self.flag_metadata,
|
||||
reason=self.reason,
|
||||
error_code=self.error_code,
|
||||
error_message=self.error_message,
|
||||
)
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType
|
||||
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType, FlagValueType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openfeature.client import ClientMetadata
|
||||
from openfeature.provider.metadata import Metadata
|
||||
|
||||
__all__ = ["Hook", "HookContext", "HookHints", "HookType"]
|
||||
__all__ = [
|
||||
"Hook",
|
||||
"HookContext",
|
||||
"HookHints",
|
||||
"HookType",
|
||||
"add_hooks",
|
||||
"clear_hooks",
|
||||
"get_hooks",
|
||||
]
|
||||
|
||||
_hooks: list[Hook] = []
|
||||
|
||||
|
||||
class HookType(Enum):
|
||||
|
@ -27,7 +38,7 @@ class HookContext:
|
|||
self,
|
||||
flag_key: str,
|
||||
flag_type: FlagType,
|
||||
default_value: typing.Any,
|
||||
default_value: FlagValueType,
|
||||
evaluation_context: EvaluationContext,
|
||||
client_metadata: typing.Optional[ClientMetadata] = None,
|
||||
provider_metadata: typing.Optional[Metadata] = None,
|
||||
|
@ -60,8 +71,8 @@ HookHints = typing.Mapping[
|
|||
float,
|
||||
str,
|
||||
datetime,
|
||||
list[typing.Any],
|
||||
dict[str, typing.Any],
|
||||
Sequence["HookHints"],
|
||||
typing.Mapping[str, "HookHints"],
|
||||
],
|
||||
]
|
||||
|
||||
|
@ -84,7 +95,7 @@ class Hook:
|
|||
def after(
|
||||
self,
|
||||
hook_context: HookContext,
|
||||
details: FlagEvaluationDetails[typing.Any],
|
||||
details: FlagEvaluationDetails[FlagValueType],
|
||||
hints: HookHints,
|
||||
) -> None:
|
||||
"""
|
||||
|
@ -112,7 +123,7 @@ class Hook:
|
|||
def finally_after(
|
||||
self,
|
||||
hook_context: HookContext,
|
||||
details: FlagEvaluationDetails[typing.Any],
|
||||
details: FlagEvaluationDetails[FlagValueType],
|
||||
hints: HookHints,
|
||||
) -> None:
|
||||
"""
|
||||
|
@ -133,3 +144,17 @@ class Hook:
|
|||
or not (False)
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
def add_hooks(hooks: list[Hook]) -> None:
|
||||
global _hooks
|
||||
_hooks = _hooks + hooks
|
||||
|
||||
|
||||
def clear_hooks() -> None:
|
||||
global _hooks
|
||||
_hooks = []
|
||||
|
||||
|
||||
def get_hooks() -> list[Hook]:
|
||||
return _hooks
|
||||
|
|
|
@ -26,7 +26,7 @@ class MappingProxyType(dict):
|
|||
__setitem__ = _immutable
|
||||
__delitem__ = _immutable
|
||||
clear = _immutable
|
||||
update = _immutable # type:ignore[assignment]
|
||||
setdefault = _immutable # type:ignore[assignment]
|
||||
pop = _immutable # type:ignore[assignment]
|
||||
update = _immutable
|
||||
setdefault = _immutable
|
||||
pop = _immutable
|
||||
popitem = _immutable
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import typing
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from enum import Enum
|
||||
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
|
@ -11,6 +12,9 @@ from openfeature.hook import Hook
|
|||
|
||||
from .metadata import Metadata
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from openfeature.flag_evaluation import FlagValueType
|
||||
|
||||
__all__ = ["AbstractProvider", "FeatureProvider", "Metadata", "ProviderStatus"]
|
||||
|
||||
|
||||
|
@ -99,19 +103,31 @@ class FeatureProvider(typing.Protocol): # pragma: no cover
|
|||
def resolve_object_details(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]: ...
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]: ...
|
||||
|
||||
async def resolve_object_details_async(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]: ...
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]: ...
|
||||
|
||||
|
||||
class AbstractProvider(FeatureProvider):
|
||||
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
# this makes sure to invoke the parent of `FeatureProvider` -> `object`
|
||||
super(FeatureProvider, self).__init__(*args, **kwargs)
|
||||
|
||||
def attach(
|
||||
self,
|
||||
on_emit: typing.Callable[
|
||||
|
@ -209,17 +225,25 @@ class AbstractProvider(FeatureProvider):
|
|||
def resolve_object_details(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
pass
|
||||
|
||||
async def resolve_object_details_async(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
return self.resolve_object_details(flag_key, default_value, evaluation_context)
|
||||
|
||||
def emit_provider_ready(self, details: ProviderEventDetails) -> None:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import typing
|
||||
|
||||
from openfeature._event_support import run_handlers_for_provider
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.evaluation_context import EvaluationContext, get_evaluation_context
|
||||
from openfeature.event import (
|
||||
ProviderEvent,
|
||||
ProviderEventDetails,
|
||||
|
@ -65,9 +65,6 @@ class ProviderRegistry:
|
|||
self._shutdown_provider(provider)
|
||||
|
||||
def _get_evaluation_context(self) -> EvaluationContext:
|
||||
# imported here to avoid circular imports
|
||||
from openfeature.api import get_evaluation_context
|
||||
|
||||
return get_evaluation_context()
|
||||
|
||||
def _initialize_provider(self, provider: FeatureProvider) -> None:
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from openfeature._backports.strenum import StrEnum
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.exception import FlagNotFoundError
|
||||
from openfeature.flag_evaluation import FlagMetadata, FlagResolutionDetails, Reason
|
||||
from openfeature.hook import Hook
|
||||
from openfeature.exception import ErrorCode
|
||||
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
||||
from openfeature.provider import AbstractProvider, Metadata
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from openfeature.flag_evaluation import FlagMetadata, FlagValueType
|
||||
from openfeature.hook import Hook
|
||||
|
||||
PASSED_IN_DEFAULT = "Passed in default"
|
||||
|
||||
|
||||
|
@ -31,7 +37,7 @@ class InMemoryFlag(typing.Generic[T_co]):
|
|||
state: State = State.ENABLED
|
||||
context_evaluator: typing.Optional[
|
||||
typing.Callable[
|
||||
["InMemoryFlag[T_co]", EvaluationContext], FlagResolutionDetails[T_co]
|
||||
[InMemoryFlag[T_co], EvaluationContext], FlagResolutionDetails[T_co]
|
||||
]
|
||||
] = None
|
||||
|
||||
|
@ -74,7 +80,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: bool,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[bool]:
|
||||
return self._resolve(flag_key, evaluation_context)
|
||||
return self._resolve(flag_key, default_value, evaluation_context)
|
||||
|
||||
async def resolve_boolean_details_async(
|
||||
self,
|
||||
|
@ -82,7 +88,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: bool,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[bool]:
|
||||
return await self._resolve_async(flag_key, evaluation_context)
|
||||
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||
|
||||
def resolve_string_details(
|
||||
self,
|
||||
|
@ -90,7 +96,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: str,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[str]:
|
||||
return self._resolve(flag_key, evaluation_context)
|
||||
return self._resolve(flag_key, default_value, evaluation_context)
|
||||
|
||||
async def resolve_string_details_async(
|
||||
self,
|
||||
|
@ -98,7 +104,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: str,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[str]:
|
||||
return await self._resolve_async(flag_key, evaluation_context)
|
||||
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||
|
||||
def resolve_integer_details(
|
||||
self,
|
||||
|
@ -106,7 +112,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: int,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[int]:
|
||||
return self._resolve(flag_key, evaluation_context)
|
||||
return self._resolve(flag_key, default_value, evaluation_context)
|
||||
|
||||
async def resolve_integer_details_async(
|
||||
self,
|
||||
|
@ -114,7 +120,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: int,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[int]:
|
||||
return await self._resolve_async(flag_key, evaluation_context)
|
||||
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||
|
||||
def resolve_float_details(
|
||||
self,
|
||||
|
@ -122,7 +128,7 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: float,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[float]:
|
||||
return self._resolve(flag_key, evaluation_context)
|
||||
return self._resolve(flag_key, default_value, evaluation_context)
|
||||
|
||||
async def resolve_float_details_async(
|
||||
self,
|
||||
|
@ -130,37 +136,52 @@ class InMemoryProvider(AbstractProvider):
|
|||
default_value: float,
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[float]:
|
||||
return await self._resolve_async(flag_key, evaluation_context)
|
||||
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||
|
||||
def resolve_object_details(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||
return self._resolve(flag_key, evaluation_context)
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
return self._resolve(flag_key, default_value, evaluation_context)
|
||||
|
||||
async def resolve_object_details_async(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||
return await self._resolve_async(flag_key, evaluation_context)
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
return await self._resolve_async(flag_key, default_value, evaluation_context)
|
||||
|
||||
def _resolve(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: V,
|
||||
evaluation_context: typing.Optional[EvaluationContext],
|
||||
) -> FlagResolutionDetails[V]:
|
||||
flag = self._flags.get(flag_key)
|
||||
if flag is None:
|
||||
raise FlagNotFoundError(f"Flag '{flag_key}' not found")
|
||||
return FlagResolutionDetails(
|
||||
value=default_value,
|
||||
reason=Reason.ERROR,
|
||||
error_code=ErrorCode.FLAG_NOT_FOUND,
|
||||
error_message=f"Flag '{flag_key}' not found",
|
||||
)
|
||||
return flag.resolve(evaluation_context)
|
||||
|
||||
async def _resolve_async(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: V,
|
||||
evaluation_context: typing.Optional[EvaluationContext],
|
||||
) -> FlagResolutionDetails[V]:
|
||||
return self._resolve(flag_key, evaluation_context)
|
||||
return self._resolve(flag_key, default_value, evaluation_context)
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import typing
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from collections.abc import Sequence
|
||||
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
||||
from openfeature.hook import Hook
|
||||
from openfeature.provider import AbstractProvider, Metadata
|
||||
from openfeature.provider import AbstractProvider
|
||||
from openfeature.provider.no_op_metadata import NoOpMetadata
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.flag_evaluation import FlagValueType
|
||||
from openfeature.hook import Hook
|
||||
from openfeature.provider import Metadata
|
||||
|
||||
PASSED_IN_DEFAULT = "Passed in default"
|
||||
|
||||
|
||||
|
@ -67,9 +74,13 @@ class NoOpProvider(AbstractProvider):
|
|||
def resolve_object_details(
|
||||
self,
|
||||
flag_key: str,
|
||||
default_value: typing.Union[dict, list],
|
||||
default_value: typing.Union[
|
||||
Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
||||
],
|
||||
evaluation_context: typing.Optional[EvaluationContext] = None,
|
||||
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
||||
) -> FlagResolutionDetails[
|
||||
typing.Union[Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
||||
]:
|
||||
return FlagResolutionDetails(
|
||||
value=default_value,
|
||||
reason=Reason.DEFAULT,
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import typing
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
|
||||
from openfeature.exception import ErrorCode
|
||||
from openfeature.flag_evaluation import FlagEvaluationDetails, Reason
|
||||
from openfeature.hook import HookContext
|
||||
from openfeature.telemetry.attributes import TelemetryAttribute
|
||||
from openfeature.telemetry.body import TelemetryBodyField
|
||||
from openfeature.telemetry.metadata import TelemetryFlagMetadata
|
||||
|
||||
__all__ = [
|
||||
"EvaluationEvent",
|
||||
"TelemetryAttribute",
|
||||
"TelemetryBodyField",
|
||||
"TelemetryFlagMetadata",
|
||||
"create_evaluation_event",
|
||||
]
|
||||
|
||||
FLAG_EVALUATION_EVENT_NAME = "feature_flag.evaluation"
|
||||
|
||||
T_co = typing.TypeVar("T_co", covariant=True)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvaluationEvent(typing.Generic[T_co]):
|
||||
name: str
|
||||
attributes: Mapping[TelemetryAttribute, typing.Union[str, T_co]]
|
||||
body: Mapping[TelemetryBodyField, T_co]
|
||||
|
||||
|
||||
def create_evaluation_event(
|
||||
hook_context: HookContext, details: FlagEvaluationDetails[T_co]
|
||||
) -> EvaluationEvent[T_co]:
|
||||
attributes = {
|
||||
TelemetryAttribute.KEY: details.flag_key,
|
||||
TelemetryAttribute.EVALUATION_REASON: (
|
||||
details.reason or Reason.UNKNOWN
|
||||
).lower(),
|
||||
}
|
||||
body = {}
|
||||
|
||||
if variant := details.variant:
|
||||
attributes[TelemetryAttribute.VARIANT] = variant
|
||||
else:
|
||||
body[TelemetryBodyField.VALUE] = details.value
|
||||
|
||||
context_id = details.flag_metadata.get(
|
||||
TelemetryFlagMetadata.CONTEXT_ID, hook_context.evaluation_context.targeting_key
|
||||
)
|
||||
if context_id:
|
||||
attributes[TelemetryAttribute.CONTEXT_ID] = typing.cast("str", context_id)
|
||||
|
||||
if set_id := details.flag_metadata.get(TelemetryFlagMetadata.FLAG_SET_ID):
|
||||
attributes[TelemetryAttribute.SET_ID] = typing.cast("str", set_id)
|
||||
|
||||
if version := details.flag_metadata.get(TelemetryFlagMetadata.VERSION):
|
||||
attributes[TelemetryAttribute.VERSION] = typing.cast("str", version)
|
||||
|
||||
if metadata := hook_context.provider_metadata:
|
||||
attributes[TelemetryAttribute.PROVIDER_NAME] = metadata.name
|
||||
|
||||
if details.reason == Reason.ERROR:
|
||||
attributes[TelemetryAttribute.ERROR_TYPE] = (
|
||||
details.error_code or ErrorCode.GENERAL
|
||||
).lower()
|
||||
|
||||
if err_msg := details.error_message:
|
||||
attributes[TelemetryAttribute.EVALUATION_ERROR_MESSAGE] = err_msg
|
||||
|
||||
return EvaluationEvent(
|
||||
name=FLAG_EVALUATION_EVENT_NAME,
|
||||
attributes=attributes,
|
||||
body=body,
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
from openfeature._backports.strenum import StrEnum
|
||||
|
||||
|
||||
class TelemetryAttribute(StrEnum):
|
||||
"""
|
||||
The attributes of an OpenTelemetry compliant event for flag evaluation.
|
||||
|
||||
See: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
|
||||
"""
|
||||
|
||||
CONTEXT_ID = "feature_flag.context.id"
|
||||
ERROR_TYPE = "error.type"
|
||||
EVALUATION_ERROR_MESSAGE = "feature_flag.evaluation.error.message"
|
||||
EVALUATION_REASON = "feature_flag.evaluation.reason"
|
||||
KEY = "feature_flag.key"
|
||||
PROVIDER_NAME = "feature_flag.provider_name"
|
||||
SET_ID = "feature_flag.set.id"
|
||||
VARIANT = "feature_flag.variant"
|
||||
VERSION = "feature_flag.version"
|
|
@ -0,0 +1,11 @@
|
|||
from openfeature._backports.strenum import StrEnum
|
||||
|
||||
|
||||
class TelemetryBodyField(StrEnum):
|
||||
"""
|
||||
OpenTelemetry event body fields.
|
||||
|
||||
See: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
|
||||
"""
|
||||
|
||||
VALUE = "value"
|
|
@ -0,0 +1,13 @@
|
|||
from openfeature._backports.strenum import StrEnum
|
||||
|
||||
|
||||
class TelemetryFlagMetadata(StrEnum):
|
||||
"""
|
||||
Well-known flag metadata attributes for telemetry events.
|
||||
|
||||
See: https://openfeature.dev/specification/appendix-d/#flag-metadata
|
||||
"""
|
||||
|
||||
CONTEXT_ID = "contextId"
|
||||
FLAG_SET_ID = "flagSetId"
|
||||
VERSION = "version"
|
|
@ -1,6 +1,10 @@
|
|||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.transaction_context.context_var_transaction_context_propagator import (
|
||||
ContextVarsTransactionContextPropagator,
|
||||
)
|
||||
from openfeature.transaction_context.no_op_transaction_context_propagator import (
|
||||
NoOpTransactionContextPropagator,
|
||||
)
|
||||
from openfeature.transaction_context.transaction_context_propagator import (
|
||||
TransactionContextPropagator,
|
||||
)
|
||||
|
@ -8,4 +12,29 @@ from openfeature.transaction_context.transaction_context_propagator import (
|
|||
__all__ = [
|
||||
"ContextVarsTransactionContextPropagator",
|
||||
"TransactionContextPropagator",
|
||||
"get_transaction_context",
|
||||
"set_transaction_context",
|
||||
"set_transaction_context_propagator",
|
||||
]
|
||||
|
||||
_evaluation_transaction_context_propagator: TransactionContextPropagator = (
|
||||
NoOpTransactionContextPropagator()
|
||||
)
|
||||
|
||||
|
||||
def set_transaction_context_propagator(
|
||||
transaction_context_propagator: TransactionContextPropagator,
|
||||
) -> None:
|
||||
global _evaluation_transaction_context_propagator
|
||||
_evaluation_transaction_context_propagator = transaction_context_propagator
|
||||
|
||||
|
||||
def get_transaction_context() -> EvaluationContext:
|
||||
return _evaluation_transaction_context_propagator.get_transaction_context()
|
||||
|
||||
|
||||
def set_transaction_context(evaluation_context: EvaluationContext) -> None:
|
||||
global _evaluation_transaction_context_propagator
|
||||
_evaluation_transaction_context_propagator.set_transaction_context(
|
||||
evaluation_context
|
||||
)
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.8.0"
|
||||
__version__ = "0.8.2"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# pyproject.toml
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["uv_build~=0.8.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[project]
|
||||
name = "openfeature_sdk"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
description = "Standardizing Feature Flagging for Everyone"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "OpenFeature", email = "openfeature-core@groups.io" }]
|
||||
|
@ -27,42 +27,22 @@ requires-python = ">=3.9"
|
|||
[project.urls]
|
||||
Homepage = "https://github.com/open-feature/python-sdk"
|
||||
|
||||
[tool.hatch]
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
dependencies = [
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"behave",
|
||||
"coverage[toml]>=6.5",
|
||||
"pytest",
|
||||
"pytest-asyncio"
|
||||
"pytest-asyncio",
|
||||
"pre-commit"
|
||||
]
|
||||
|
||||
[tool.hatch.envs.default.scripts]
|
||||
test = "pytest {args:tests}"
|
||||
test-cov = "coverage run -m pytest {args:tests}"
|
||||
cov-report = [
|
||||
"coverage xml",
|
||||
]
|
||||
cov = [
|
||||
"test-cov",
|
||||
"cov-report",
|
||||
]
|
||||
e2e = [
|
||||
"git submodule add --force https://github.com/open-feature/spec.git spec",
|
||||
"cp spec/specification/assets/gherkin/* tests/features/",
|
||||
"behave tests/features/",
|
||||
"rm tests/features/*.feature",
|
||||
]
|
||||
[tool.uv]
|
||||
required-version = "~=0.8.0"
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
exclude = [
|
||||
".gitignore",
|
||||
"test-harness",
|
||||
"venv",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["openfeature"]
|
||||
[tool.uv.build-backend]
|
||||
module-name = "openfeature"
|
||||
module-root = ""
|
||||
namespace = true
|
||||
|
||||
[tool.mypy]
|
||||
files = "openfeature"
|
||||
|
@ -78,6 +58,12 @@ disallow_any_generics = false
|
|||
[tool.pytest.ini_options]
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_also = [
|
||||
"if TYPE_CHECKING:",
|
||||
"if typing.TYPE_CHECKING:",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
".git",
|
||||
|
@ -128,3 +114,13 @@ max-statements = 30
|
|||
[tool.ruff.lint.pyupgrade]
|
||||
# Preserve types, even if a file imports `from __future__ import annotations`.
|
||||
keep-runtime-typing = true
|
||||
|
||||
[project.scripts]
|
||||
# workaround while UV doesn't support scripts directly in the pyproject.toml
|
||||
# see: https://github.com/astral-sh/uv/issues/5903
|
||||
test = "scripts.scripts:test"
|
||||
test-cov = "scripts.scripts:test_cov"
|
||||
cov-report = "scripts.scripts:cov_report"
|
||||
cov = "scripts.scripts:cov"
|
||||
e2e = "scripts.scripts:e2e"
|
||||
precommit = "scripts.scripts:precommit"
|
|
@ -8,5 +8,8 @@
|
|||
},
|
||||
"pre-commit": {
|
||||
"enabled": true
|
||||
},
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# ruff: noqa: S602, S607
|
||||
import subprocess
|
||||
|
||||
|
||||
def test():
|
||||
"""Run pytest tests."""
|
||||
subprocess.run("pytest tests", shell=True, check=True)
|
||||
|
||||
|
||||
def test_cov():
|
||||
"""Run tests with coverage."""
|
||||
subprocess.run("coverage run -m pytest tests", shell=True, check=True)
|
||||
|
||||
|
||||
def cov_report():
|
||||
"""Generate coverage report."""
|
||||
subprocess.run("coverage xml", shell=True, check=True)
|
||||
|
||||
|
||||
def cov():
|
||||
"""Run tests with coverage and generate report."""
|
||||
test_cov()
|
||||
cov_report()
|
||||
|
||||
|
||||
def e2e():
|
||||
"""Run end-to-end tests."""
|
||||
subprocess.run("git submodule update --init --recursive", shell=True, check=True)
|
||||
subprocess.run(
|
||||
"cp spec/specification/assets/gherkin/* tests/features/", shell=True, check=True
|
||||
)
|
||||
subprocess.run("behave tests/features/", shell=True, check=True)
|
||||
subprocess.run("rm tests/features/*.feature", shell=True, check=True)
|
||||
|
||||
|
||||
def precommit():
|
||||
"""Run pre-commit hooks."""
|
||||
subprocess.run("uv run pre-commit run --all-files", shell=True, check=True)
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0cd553d85f03b0b7ab983a988ce1720a3190bc88
|
|
@ -2,7 +2,7 @@ from numbers import Number
|
|||
|
||||
import pytest
|
||||
|
||||
from openfeature.exception import FlagNotFoundError
|
||||
from openfeature.exception import ErrorCode
|
||||
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
||||
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
|
||||
|
||||
|
@ -22,11 +22,18 @@ async def test_should_handle_unknown_flags_correctly():
|
|||
# Given
|
||||
provider = InMemoryProvider({})
|
||||
# When
|
||||
with pytest.raises(FlagNotFoundError):
|
||||
provider.resolve_boolean_details(flag_key="Key", default_value=True)
|
||||
with pytest.raises(FlagNotFoundError):
|
||||
await provider.resolve_integer_details_async(flag_key="Key", default_value=1)
|
||||
flag_sync = provider.resolve_boolean_details(flag_key="Key", default_value=True)
|
||||
flag_async = await provider.resolve_boolean_details_async(
|
||||
flag_key="Key", default_value=True
|
||||
)
|
||||
# Then
|
||||
assert flag_sync == flag_async
|
||||
for flag in [flag_sync, flag_async]:
|
||||
assert flag is not None
|
||||
assert flag.value is True
|
||||
assert flag.reason == Reason.ERROR
|
||||
assert flag.error_code == ErrorCode.FLAG_NOT_FOUND
|
||||
assert flag.error_message == "Flag 'Key' not found"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.exception import ErrorCode
|
||||
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType, Reason
|
||||
from openfeature.hook import HookContext
|
||||
from openfeature.provider import Metadata
|
||||
from openfeature.telemetry import (
|
||||
TelemetryAttribute,
|
||||
TelemetryBodyField,
|
||||
TelemetryFlagMetadata,
|
||||
create_evaluation_event,
|
||||
)
|
||||
|
||||
|
||||
def test_create_evaluation_event():
|
||||
# given
|
||||
hook_context = HookContext(
|
||||
flag_key="flag_key",
|
||||
flag_type=FlagType.BOOLEAN,
|
||||
default_value=True,
|
||||
evaluation_context=EvaluationContext(),
|
||||
provider_metadata=Metadata(name="test_provider"),
|
||||
)
|
||||
details = FlagEvaluationDetails(
|
||||
flag_key=hook_context.flag_key,
|
||||
value=False,
|
||||
reason=Reason.CACHED,
|
||||
)
|
||||
|
||||
# when
|
||||
event = create_evaluation_event(hook_context=hook_context, details=details)
|
||||
|
||||
# then
|
||||
assert event.name == "feature_flag.evaluation"
|
||||
assert event.attributes[TelemetryAttribute.KEY] == "flag_key"
|
||||
assert event.attributes[TelemetryAttribute.EVALUATION_REASON] == "cached"
|
||||
assert event.attributes[TelemetryAttribute.PROVIDER_NAME] == "test_provider"
|
||||
assert event.body[TelemetryBodyField.VALUE] is False
|
||||
|
||||
|
||||
def test_create_evaluation_event_with_variant():
|
||||
# given
|
||||
hook_context = HookContext("flag_key", FlagType.BOOLEAN, True, EvaluationContext())
|
||||
details = FlagEvaluationDetails(
|
||||
flag_key=hook_context.flag_key,
|
||||
value=True,
|
||||
variant="true",
|
||||
)
|
||||
|
||||
# when
|
||||
event = create_evaluation_event(hook_context=hook_context, details=details)
|
||||
|
||||
# then
|
||||
assert event.name == "feature_flag.evaluation"
|
||||
assert event.attributes[TelemetryAttribute.KEY] == "flag_key"
|
||||
assert event.attributes[TelemetryAttribute.VARIANT] == "true"
|
||||
assert event.attributes[TelemetryAttribute.EVALUATION_REASON] == "unknown"
|
||||
|
||||
|
||||
def test_create_evaluation_event_with_metadata():
|
||||
# given
|
||||
hook_context = HookContext("flag_key", FlagType.BOOLEAN, True, EvaluationContext())
|
||||
details = FlagEvaluationDetails(
|
||||
flag_key=hook_context.flag_key,
|
||||
value=False,
|
||||
flag_metadata={
|
||||
TelemetryFlagMetadata.CONTEXT_ID: "5157782b-2203-4c80-a857-dbbd5e7761db",
|
||||
TelemetryFlagMetadata.FLAG_SET_ID: "proj-1",
|
||||
TelemetryFlagMetadata.VERSION: "v1",
|
||||
},
|
||||
)
|
||||
|
||||
# when
|
||||
event = create_evaluation_event(hook_context=hook_context, details=details)
|
||||
|
||||
# then
|
||||
assert (
|
||||
event.attributes[TelemetryAttribute.CONTEXT_ID]
|
||||
== "5157782b-2203-4c80-a857-dbbd5e7761db"
|
||||
)
|
||||
assert event.attributes[TelemetryAttribute.SET_ID] == "proj-1"
|
||||
assert event.attributes[TelemetryAttribute.VERSION] == "v1"
|
||||
|
||||
|
||||
def test_create_evaluation_event_with_error():
|
||||
# given
|
||||
hook_context = HookContext("flag_key", FlagType.BOOLEAN, True, EvaluationContext())
|
||||
details = FlagEvaluationDetails(
|
||||
flag_key=hook_context.flag_key,
|
||||
value=False,
|
||||
reason=Reason.ERROR,
|
||||
error_code=ErrorCode.FLAG_NOT_FOUND,
|
||||
error_message="flag error",
|
||||
)
|
||||
|
||||
# when
|
||||
event = create_evaluation_event(hook_context=hook_context, details=details)
|
||||
|
||||
# then
|
||||
assert event.attributes[TelemetryAttribute.EVALUATION_REASON] == "error"
|
||||
assert event.attributes[TelemetryAttribute.ERROR_TYPE] == "flag_not_found"
|
||||
assert event.attributes[TelemetryAttribute.EVALUATION_ERROR_MESSAGE] == "flag error"
|
|
@ -1,4 +1,4 @@
|
|||
import asyncio
|
||||
import inspect
|
||||
import time
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
@ -8,11 +8,11 @@ import pytest
|
|||
|
||||
from openfeature import api
|
||||
from openfeature.api import add_hooks, clear_hooks, get_client, set_provider
|
||||
from openfeature.client import GeneralError, OpenFeatureClient, _typecheck_flag_value
|
||||
from openfeature.client import OpenFeatureClient, _typecheck_flag_value
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.event import EventDetails, ProviderEvent, ProviderEventDetails
|
||||
from openfeature.exception import ErrorCode, OpenFeatureError
|
||||
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
||||
from openfeature.flag_evaluation import FlagResolutionDetails, FlagType, Reason
|
||||
from openfeature.hook import Hook
|
||||
from openfeature.provider import FeatureProvider, ProviderStatus
|
||||
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
|
||||
|
@ -68,7 +68,7 @@ async def test_should_get_flag_value_based_on_method_type(
|
|||
# Given
|
||||
# When
|
||||
method = getattr(no_op_provider_client, get_method)
|
||||
if asyncio.iscoroutinefunction(method):
|
||||
if inspect.iscoroutinefunction(method):
|
||||
flag = await method(flag_key="Key", default_value=default_value)
|
||||
else:
|
||||
flag = method(flag_key="Key", default_value=default_value)
|
||||
|
@ -126,7 +126,7 @@ async def test_should_get_flag_detail_based_on_method_type(
|
|||
# Given
|
||||
# When
|
||||
method = getattr(no_op_provider_client, get_method)
|
||||
if asyncio.iscoroutinefunction(method):
|
||||
if inspect.iscoroutinefunction(method):
|
||||
flag = await method(flag_key="Key", default_value=default_value)
|
||||
else:
|
||||
flag = method(flag_key="Key", default_value=default_value)
|
||||
|
@ -364,15 +364,27 @@ async def test_client_type_mismatch_exceptions():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_general_exception():
|
||||
async def test_typecheck_flag_value_general_error():
|
||||
# Given
|
||||
flag_value = "A"
|
||||
flag_type = None
|
||||
# When
|
||||
with pytest.raises(GeneralError) as e:
|
||||
flag_type = _typecheck_flag_value(flag_value, flag_type)
|
||||
err = _typecheck_flag_value(value=flag_value, flag_type=flag_type)
|
||||
# Then
|
||||
assert e.value.error_message == "Unknown flag type"
|
||||
assert err.error_code == ErrorCode.GENERAL
|
||||
assert err.error_message == "Unknown flag type"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_typecheck_flag_value_type_mismatch_error():
|
||||
# Given
|
||||
flag_value = "A"
|
||||
flag_type = FlagType.BOOLEAN
|
||||
# When
|
||||
err = _typecheck_flag_value(value=flag_value, flag_type=flag_type)
|
||||
# Then
|
||||
assert err.error_code == ErrorCode.TYPE_MISMATCH
|
||||
assert err.error_message == "Expected type <class 'bool'> but got <class 'str'>"
|
||||
|
||||
|
||||
def test_provider_events():
|
||||
|
@ -526,12 +538,20 @@ def test_client_should_merge_contexts():
|
|||
invocation_context = EvaluationContext(
|
||||
targeting_key="invocation", attributes={"invocation_attr": "invocation_value"}
|
||||
)
|
||||
client.get_boolean_details("flag", False, invocation_context)
|
||||
flag_input = "flag"
|
||||
flag_default = False
|
||||
client.get_boolean_details(flag_input, flag_default, invocation_context)
|
||||
|
||||
# Retrieve the call arguments
|
||||
args, kwargs = provider.resolve_boolean_details.call_args
|
||||
flag_key, default_value, context = args
|
||||
flag_key, default_value, context = (
|
||||
kwargs["flag_key"],
|
||||
kwargs["default_value"],
|
||||
kwargs["evaluation_context"],
|
||||
)
|
||||
|
||||
assert flag_key == flag_input
|
||||
assert default_value is flag_default
|
||||
assert context.targeting_key == "invocation" # Last one in the merge chain
|
||||
assert context.attributes["global_attr"] == "global_value"
|
||||
assert context.attributes["transaction_attr"] == "transaction_value"
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
from openfeature.api import (
|
||||
set_provider,
|
||||
set_transaction_context,
|
||||
set_transaction_context_propagator,
|
||||
)
|
||||
from openfeature.client import OpenFeatureClient
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.hook import Hook
|
||||
from openfeature.provider.no_op_provider import NoOpProvider
|
||||
from openfeature.transaction_context import ContextVarsTransactionContextPropagator
|
||||
|
||||
|
||||
class TransactionContextHook(Hook):
|
||||
def __init__(self):
|
||||
self.before_called = False
|
||||
self.transaction_attr_value = None
|
||||
|
||||
def before(self, hook_context, hints):
|
||||
self.before_called = True
|
||||
# Check if the transaction context attribute is in the hook context
|
||||
if "transaction_attr" in hook_context.evaluation_context.attributes:
|
||||
self.transaction_attr_value = hook_context.evaluation_context.attributes[
|
||||
"transaction_attr"
|
||||
]
|
||||
return None
|
||||
|
||||
|
||||
def test_transaction_context_merged_into_hook_context():
|
||||
"""Test that transaction context is merged into the hook context's evaluation context."""
|
||||
set_transaction_context_propagator(ContextVarsTransactionContextPropagator())
|
||||
|
||||
provider = NoOpProvider()
|
||||
set_provider(provider)
|
||||
|
||||
client = OpenFeatureClient(domain=None, version=None)
|
||||
|
||||
hook = TransactionContextHook()
|
||||
client.add_hooks([hook])
|
||||
|
||||
transaction_context = EvaluationContext(
|
||||
targeting_key="transaction",
|
||||
attributes={"transaction_attr": "transaction_value"},
|
||||
)
|
||||
set_transaction_context(transaction_context)
|
||||
|
||||
client.get_boolean_value(flag_key="test-flag", default_value=False)
|
||||
|
||||
assert hook.before_called, "Hook's before method was not called"
|
||||
assert hook.transaction_attr_value == "transaction_value", (
|
||||
"Transaction context attribute was not found in hook context"
|
||||
)
|
|
@ -0,0 +1,456 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[[package]]
|
||||
name = "backports-asyncio-runner"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "behave"
|
||||
version = "1.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "parse" },
|
||||
{ name = "parse-type" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c8/4b/d0a8c23b6c8985e5544ea96d27105a273ea22051317f850c2cdbf2029fe4/behave-1.2.6.tar.gz", hash = "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", size = 701696, upload-time = "2018-02-25T20:06:38.851Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/6c/ec9169548b6c4cb877aaa6773408ca08ae2a282805b958dbc163cb19822d/behave-1.2.6-py2.py3-none-any.whl", hash = "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c", size = 136779, upload-time = "2018-02-25T20:06:34.436Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/8f/6ac7fbb29e35645065f7be835bfe3e0cce567f80390de2f3db65d83cb5e3/coverage-7.10.0.tar.gz", hash = "sha256:2768885aef484b5dcde56262cbdfba559b770bfc46994fe9485dc3614c7a5867", size = 819816, upload-time = "2025-07-24T16:53:00.896Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/f6/b2366476b1f48134757f2a42aaf00e7ce8e734eea5f3cf022df113116174/coverage-7.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbd823f7ea5286c26406ad9e54268544d82f3d1cadb6d4f3b85e9877f0cab1ef", size = 214813, upload-time = "2025-07-24T16:50:18.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/d1/7e26bb4c41ed1b9aca4550187ca42557d79c70d318414a703d814858eacb/coverage-7.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab3f7a5dbaab937df0b9e9e8ec6eab235ba9a6f29d71fd3b24335affaed886cc", size = 215206, upload-time = "2025-07-24T16:50:21.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/71/d5ae128557c8d0ce0156eb1e980e5c6e6f7e54ef3e998c87ab4b3679ff45/coverage-7.10.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8c63aaf850523d8cbe3f5f1a5c78f689b223797bef902635f2493ab43498f36c", size = 242171, upload-time = "2025-07-24T16:50:23.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/87/d586a627e3b61cfe631ebcf3d8a38bf9085142800d2ac434bc20f3699880/coverage-7.10.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c3133ce3fa84023f7c6921c4dca711be0b658784c5a51a797168229eae26172", size = 243431, upload-time = "2025-07-24T16:50:24.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/cc/ff5c6f4f99a987ebd18a3350194377c7cefee9ddd6e532ede83a0a1f332c/coverage-7.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3747d1d0af85b17d3a156cd30e4bbacf893815e846dc6c07050e9769da2b138e", size = 245288, upload-time = "2025-07-24T16:50:26.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/d9/2758e73d7fe496c04dd715af8bb8856354a1ad4cc11553d9096c4b35dc86/coverage-7.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:241923b350437f6a7cb343d9df72998305ef940c3c40009f06e05029a047677c", size = 243235, upload-time = "2025-07-24T16:50:28.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/9b/3c273dde651d83484992d7e7bcd9cd84a363f01026caf69716390bd79e0d/coverage-7.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13e82e499309307104d58ac66f9eed237f7aaceab4325416645be34064d9a2be", size = 241909, upload-time = "2025-07-24T16:50:30.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/7c/006d9f66035c4d414ea642d990854a30c23145551315bd0b38100daee168/coverage-7.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf73cdde4f6c9cd4457b00bf1696236796ac3a241f859a55e0f84a4c58326a7f", size = 242202, upload-time = "2025-07-24T16:50:32.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/42/80d8747f77c63593a2114c7299df52f7568168e4fd882d7d5ebe8181564f/coverage-7.10.0-cp310-cp310-win32.whl", hash = "sha256:2396e13275b37870a3345f58bce8b15a7e0a985771d13a4b16ce9129954e07d6", size = 217311, upload-time = "2025-07-24T16:50:33.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/8b/fe04c3851e5d290524f563a8a564c7e5dcd6b5ca35ed689ce662346de230/coverage-7.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d45c7c71fb3d2da92ab893602e3f28f2d1560cec765a27e1824a6e0f7e92cfd", size = 218199, upload-time = "2025-07-24T16:50:36.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/5d/0d1ee021439e3b8b1e86ba92465f5a8d8e15b0222dcdd705606ef089f4fe/coverage-7.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4abc01843581a6f9dd72d4d15761861190973a2305416639435ef509288f7a04", size = 214934, upload-time = "2025-07-24T16:50:38.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b2/1e0727327e473aa1a68ca1c9922818a06061d05d44e0c5330109d091b525/coverage-7.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2093297773111d7d748fe4a99b68747e57994531fb5c57bbe439af17c11c169", size = 215320, upload-time = "2025-07-24T16:50:39.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/17/d231e37236863ae3bed7c51615af6b6fc89639c88adf35766d2880dcd7c7/coverage-7.10.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58240e27815bf105bd975c2fd42e700839f93d5aad034ef976411193ca32dbfd", size = 245321, upload-time = "2025-07-24T16:50:41.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/77/a285aba35bf6ec12c466474931410ef0e6fa85542169009443868e98820a/coverage-7.10.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d019eac999b40ad48521ea057958b07a9f549c0c6d257a20e5c7c4ba91af8d1c", size = 247155, upload-time = "2025-07-24T16:50:43.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/82/50512eafdd5938a7aa1550014e37fa1c2ca85516bfd85ffeb2f03eff052a/coverage-7.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e0a1f5454bc80faf4ceab10d1d48f025f92046c9c0f3bec2e1a9dda55137f8", size = 249320, upload-time = "2025-07-24T16:50:44.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/7b/0ec1dc75c8f4d940d03d477b1e07269b4804dcab74ad1e294d40310aba47/coverage-7.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a93dd7759c416dd1cc754123b926d065055cb9a33b6699e64a1e5bdfae1ff459", size = 247047, upload-time = "2025-07-24T16:50:46.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5b/40f9b78ae98c2f511a2b062660906e126aadcd35870b9190a4f10f2820ae/coverage-7.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7b3d737266048368a6ffd68f1ecd662c54de56535c82eb8f98a55ac216a72cbd", size = 245078, upload-time = "2025-07-24T16:50:47.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/f6/672c2a728e77846be7fcc4baaa003e0df86a2174aeb8921d132c14c333d4/coverage-7.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:93227c2707cb0effd9163cd0d8f0d9ab628982f7a3e915d6d64c7107867b9a07", size = 245686, upload-time = "2025-07-24T16:50:49.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/f3/fa078f0bfae7f0e6b14c426f9cb095f4809314d926c89b9a2641fb4ca482/coverage-7.10.0-cp311-cp311-win32.whl", hash = "sha256:69270af3014ab3058ad6108c6d0e218166f568b5a7a070dc3d62c0a63aca1c4d", size = 217350, upload-time = "2025-07-24T16:50:50.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/40/eefc3ebb9e458e3dc5db00e6b838969375577a09a8a39986d79cfa283175/coverage-7.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c16bbb661a7b4dafac0ab69e44d6dbcc6a64c4d93aefd89edc6f8911b6ab4a", size = 218235, upload-time = "2025-07-24T16:50:52.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/b8/3b53890c3ad52279eaea594a86bceaf04fcc0aed16856ff81531f75735f4/coverage-7.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:14e7c23fcb74ed808efb4eb48fcd25a759f0e20f685f83266d1df174860e4733", size = 216668, upload-time = "2025-07-24T16:50:53.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/b4/7b419bb368c9f0b88889cb24805164f6e5550d7183fb59524f6173e0cf0b/coverage-7.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2adcfdaf3b4d69b0c64ad024fe9dd6996782b52790fb6033d90f36f39e287df", size = 215124, upload-time = "2025-07-24T16:50:55.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/15/d862a806734c7e50fd5350cef18e22832ba3cdad282ca5660d6fd49def92/coverage-7.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d7b27c2c0840e8eeff3f1963782bd9d3bc767488d2e67a31de18d724327f9f6", size = 215364, upload-time = "2025-07-24T16:50:57.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/93/4671ca5b2f3650c961a01252cbad96cb41f7c0c2b85c6062f27740a66b06/coverage-7.10.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0ed50429786e935517570b08576a661fd79032e6060985ab492b9d39ba8e66ee", size = 246369, upload-time = "2025-07-24T16:50:59.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/79/2ca676c712d0540df0d7957a4266232980b60858a7a654846af1878cfde0/coverage-7.10.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7171c139ab6571d70460ecf788b1dcaf376bfc75a42e1946b8c031d062bbbad4", size = 248798, upload-time = "2025-07-24T16:51:01.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/c5/67e000b03ba5291f915ddd6ba7c3333e4fdee9ba003b914c8f8f2d966dfe/coverage-7.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a726aac7e6e406e403cdee4c443a13aed3ea3d67d856414c5beacac2e70c04e", size = 250260, upload-time = "2025-07-24T16:51:02.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/76/196783c425b5633db5c789b02a023858377bd73e4db4c805c2503cc42bbf/coverage-7.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2886257481a14e953e96861a00c0fe7151117a523f0470a51e392f00640bba03", size = 248171, upload-time = "2025-07-24T16:51:04.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/1f/bf86c75f42de3641b4bbeab9712ec2815a3a8f5939768077245a492fad9f/coverage-7.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:536578b79521e59c385a2e0a14a5dc2a8edd58761a966d79368413e339fc9535", size = 246368, upload-time = "2025-07-24T16:51:06.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/95/bfc9a3abef0b160404438e82ec778a0f38660c66a4b0ed94d0417d4d2290/coverage-7.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77fae95558f7804a9ceefabf3c38ad41af1da92b39781b87197c6440dcaaa967", size = 247578, upload-time = "2025-07-24T16:51:07.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/7e/4fb2a284d56fe2a3ba0c76806923014854a64e503dc8ce21e5a2e6497eea/coverage-7.10.0-cp312-cp312-win32.whl", hash = "sha256:97803e14736493eb029558e1502fe507bd6a08af277a5c8eeccf05c3e970cb84", size = 217521, upload-time = "2025-07-24T16:51:09.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/30/3ab51058b75e9931fc48594d79888396cf009910fabebe12a6a636ab7f9e/coverage-7.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c73ab554e54ffd38d114d6bc4a7115fb0c840cf6d8622211bee3da26e4bd25d", size = 218308, upload-time = "2025-07-24T16:51:11.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/34/2adc74fd132eaa1873b1688acb906b477216074ed8a37e90426eca6d2900/coverage-7.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:3ae95d5a9aedab853641026b71b2ddd01983a0a7e9bf870a20ef3c8f5d904699", size = 216706, upload-time = "2025-07-24T16:51:12.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/a7/a47f64718c2229b7860a334edd4e6ff41ec8513f3d3f4246284610344392/coverage-7.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d883fee92b9245c0120fa25b5d36de71ccd4cfc29735906a448271e935d8d86d", size = 215143, upload-time = "2025-07-24T16:51:14.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/86/14d76a409e9ffab10d5aece73ac159dbd102fc56627e203413bfc6d53b24/coverage-7.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c87e59e88268d30e33d3665ede4fbb77b513981a2df0059e7c106ca3de537586", size = 215401, upload-time = "2025-07-24T16:51:15.978Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/b3/fb5c28148a19035a3877fac4e40b044a4c97b24658c980bcf7dff18bfab8/coverage-7.10.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f669d969f669a11d6ceee0b733e491d9a50573eb92a71ffab13b15f3aa2665d4", size = 245949, upload-time = "2025-07-24T16:51:17.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/95/357559ecfe73970d2023845797361e6c2e6c2c05f970073fff186fe19dd7/coverage-7.10.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9582bd6c6771300a847d328c1c4204e751dbc339a9e249eecdc48cada41f72e6", size = 248295, upload-time = "2025-07-24T16:51:19.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/58/bac5bc43085712af201f76a24733895331c475e5ddda88ac36c1332a65e6/coverage-7.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f97e9637dc7977842776fdb7ad142075d6fa40bc1b91cb73685265e0d31d32", size = 249733, upload-time = "2025-07-24T16:51:21.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/db/104b713b3b74752ee365346677fb104765923982ae7bd93b95ca41fe256b/coverage-7.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae4fa92b6601a62367c6c9967ad32ad4e28a89af54b6bb37d740946b0e0534dd", size = 247943, upload-time = "2025-07-24T16:51:23.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/4f/bef25c797c9496cf31ae9cfa93ce96b4414cacf13688e4a6000982772fd5/coverage-7.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3a5cc8b97473e7b3623dd17a42d2194a2b49de8afecf8d7d03c8987237a9552c", size = 245914, upload-time = "2025-07-24T16:51:24.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/6b/b3efa0b506dbb9a37830d6dc862438fe3ad2833c5f889152bce24d9577cf/coverage-7.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc1cbb7f623250e047c32bd7aa1bb62ebc62608d5004d74df095e1059141ac88", size = 247296, upload-time = "2025-07-24T16:51:26.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/aa/95a845266aeacab4c57b08e0f4e0e2899b07809a18fd0c1ddef2ac2c9138/coverage-7.10.0-cp313-cp313-win32.whl", hash = "sha256:1380cc5666d778e77f1587cd88cc317158111f44d54c0dd3975f0936993284e0", size = 217566, upload-time = "2025-07-24T16:51:28.961Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/d1/27b6e5073a8026b9e0f4224f1ac53217ce589a4cdab1bee878f23bff64f0/coverage-7.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:bf03cf176af098ee578b754a03add4690b82bdfe070adfb5d192d0b1cd15cf82", size = 218337, upload-time = "2025-07-24T16:51:31.45Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/06/0e3ba498b11e2245fd96bd7e8dcdf90e1dd36d57f49f308aa650ff0561b8/coverage-7.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:8041c78cd145088116db2329b2fb6e89dc338116c962fbe654b7e9f5d72ab957", size = 216740, upload-time = "2025-07-24T16:51:33.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/8b/11529debbe3e6b39ef6e7c8912554724adc6dc10adbb617a855ecfd387eb/coverage-7.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37cc2c06052771f48651160c080a86431884db9cd62ba622cab71049b90a95b3", size = 215866, upload-time = "2025-07-24T16:51:35.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/6d/d8981310879e395f39af66536665b75135b1bc88dd21c7764e3340e9ce69/coverage-7.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:91f37270b16178b05fa107d85713d29bf21606e37b652d38646eef5f2dfbd458", size = 216083, upload-time = "2025-07-24T16:51:36.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/84/93295402de002de8b8c953bf6a1f19687174c4db7d44c1e85ffc153a772d/coverage-7.10.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f9b0b0168864d09bcb9a3837548f75121645c4cfd0efce0eb994c221955c5b10", size = 257320, upload-time = "2025-07-24T16:51:38.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/5c/d0540db4869954dac0f69ad709adcd51f3a73ab11fcc9435ee76c518944a/coverage-7.10.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0be435d3b616e7d3ee3f9ebbc0d784a213986fe5dff9c6f1042ee7cfd30157", size = 259182, upload-time = "2025-07-24T16:51:40.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/b2/d7d57a41a15ca4b47290862efd6b596d0a185bfd26f15d04db9f238aa56c/coverage-7.10.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e9aba1c4434b837b1d567a533feba5ce205e8e91179c97974b28a14c23d3a0", size = 261322, upload-time = "2025-07-24T16:51:42.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/92/fd828ae411b3da63673305617b6fbeccc09feb7dfe397d164f55a65cd880/coverage-7.10.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a0b0c481e74dfad631bdc2c883e57d8b058e5c90ba8ef087600995daf7bbec18", size = 258914, upload-time = "2025-07-24T16:51:44.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/49/4aa5f5464b2e1215640c0400c5b007e7f5cdade8bf39c55c33b02f3a8c7f/coverage-7.10.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8aec1b7c8922808a433c13cd44ace6fceac0609f4587773f6c8217a06102674b", size = 257051, upload-time = "2025-07-24T16:51:45.75Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5a/ded2346098c7f48ff6e135b5005b97de4cd9daec5c39adb4ecf3a60967da/coverage-7.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04ec59ceb3a594af0927f2e0d810e1221212abd9a2e6b5b917769ff48760b460", size = 257869, upload-time = "2025-07-24T16:51:47.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/66/e06cedb8fc7d1c96630b2f549b8cdc084e2623dcc70c900cb3b705a36a60/coverage-7.10.0-cp313-cp313t-win32.whl", hash = "sha256:b6871e62d29646eb9b3f5f92def59e7575daea1587db21f99e2b19561187abda", size = 218243, upload-time = "2025-07-24T16:51:49.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/1e/e84dd5ff35ed066bd6150e5c26fe0061ded2c59c209fd4f18db0650766c0/coverage-7.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff99cff2be44f78920b76803f782e91ffb46ccc7fa89eccccc0da3ca94285b64", size = 219334, upload-time = "2025-07-24T16:51:50.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/e0/b7b60b5dbc4e88eac0a0e9d5b4762409a59b29bf4e772b3509c8543ccaba/coverage-7.10.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3246b63501348fe47299d12c47a27cfc221cfbffa1c2d857bcc8151323a4ae4f", size = 217196, upload-time = "2025-07-24T16:51:52.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/c1/597b4fa7d6c0861d4916c4fe5c45bf30c11b31a3b07fedffed23dec5f765/coverage-7.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:1f628d91f941a375b4503cb486148dbeeffb48e17bc080e0f0adfee729361574", size = 215139, upload-time = "2025-07-24T16:51:54.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/47/07973dcad0161355cf01ff0023ab34466b735deb460a178f37163d7c800e/coverage-7.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a0e101d5af952d233557e445f42ebace20b06b4ceb615581595ced5386caa78", size = 215419, upload-time = "2025-07-24T16:51:56.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f8/c65127782da312084ef909c1531226c869bfe22dac8b92d9c609d8150131/coverage-7.10.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec4c1abbcc53f9f650acb14ea71725d88246a9e14ed42f8dd1b4e1b694e9d842", size = 245917, upload-time = "2025-07-24T16:51:58.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/97/a7f2fe79b6ae759ccc8740608cf9686ae406cc5e5591947ebbf1d679a325/coverage-7.10.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9c95f3a7f041b4cc68a8e3fecfa6366170c13ac773841049f1cd19c8650094e0", size = 248225, upload-time = "2025-07-24T16:51:59.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/d3/d2e1496d7ac3340356c5de582e08e14b02933e254924f79d18e9749269d8/coverage-7.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a2cd597b69c16d24e310611f2ed6fcfb8f09429316038c03a57e7b4f5345244", size = 249844, upload-time = "2025-07-24T16:52:01.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7e/e26d966c9cae62500e5924107974ede2e985f7d119d10ed44d102998e509/coverage-7.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5e18591906a40c2b3609196c9879136aa4a47c5405052ca6b065ab10cb0b71d0", size = 247871, upload-time = "2025-07-24T16:52:03.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/95/6a372a292dfb9d6e2cc019fc50878f7a6a5fbe704604018d7c5c1dbffb2d/coverage-7.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:485c55744252ed3f300cc1a0f5f365e684a0f2651a7aed301f7a67125906b80e", size = 245714, upload-time = "2025-07-24T16:52:05.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/7f/63da22b7bc4e82e2c1df7755223291fc94fb01942cfe75e19f2bed96129e/coverage-7.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4dabea1516e5b0e9577282b149c8015e4dceeb606da66fb8d9d75932d5799bf5", size = 247131, upload-time = "2025-07-24T16:52:07.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/af/883272555e34872879f48daea4207489cb36df249e3069e6a8a664dc6ba6/coverage-7.10.0-cp314-cp314-win32.whl", hash = "sha256:ac455f0537af22333fdc23b824cff81110dff2d47300bb2490f947b7c9a16017", size = 217804, upload-time = "2025-07-24T16:52:09.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/f6/7afc3439994b7f7311d858438d49eef8b06eadbf2322502d921a110fae1e/coverage-7.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:b3c94b532f52f95f36fbfde3e178510a4d04eea640b484b2fe8f1491338dc653", size = 218596, upload-time = "2025-07-24T16:52:11.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/99/7c715cfa155609ee3e71bc81b4d1265e1a9b79ad00cc3d19917ea736cbac/coverage-7.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:2f807f2c3a9da99c80dfa73f09ef5fc3bd21e70c73ba1c538f23396a3a772252", size = 216960, upload-time = "2025-07-24T16:52:12.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/18/5cb476346d3842f2e42cd92614a91921ebad38aa97aba63f2aab51919e35/coverage-7.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0a889ef25215990f65073c32cadf37483363a6a22914186dedc15a6b1a597d50", size = 215881, upload-time = "2025-07-24T16:52:14.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/1b/c066d6836f4c1940a8df14894a5ec99db362838fdd9eee9fb7efe0e561d2/coverage-7.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39c638ecf3123805bacbf71aff8091e93af490c676fca10ab4e442375076e483", size = 216087, upload-time = "2025-07-24T16:52:16.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/57/f0996fd468e70d4d24d69eba10ecc2b913c2e85d9f3c1bb2075ad7554c05/coverage-7.10.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f2f2c0df0cbcf7dffa14f88a99c530cdef3f4fcfe935fa4f95d28be2e7ebc570", size = 257408, upload-time = "2025-07-24T16:52:18.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/78/c9f308b2b986cc685d4964a3b829b053817a07d7ba14ff124cf06154402e/coverage-7.10.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:048d19a5d641a2296745ab59f34a27b89a08c48d6d432685f22aac0ec1ea447f", size = 259373, upload-time = "2025-07-24T16:52:20.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/13/192827b71da71255d3554cb7dc289bce561cb281bda27e1b0dd19d88e47d/coverage-7.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1209b65d302d7a762004be37ab9396cbd8c99525ed572bdf455477e3a9449e06", size = 261495, upload-time = "2025-07-24T16:52:23.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/5c/cf4694353405abbb440a94468df8e5c4dbf884635da1f056b43be7284d28/coverage-7.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e44aa79a36a7a0aec6ea109905a4a7c28552d90f34e5941b36217ae9556657d5", size = 258970, upload-time = "2025-07-24T16:52:25.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/83/fb45dac65c42eff6ce4153fe51b9f2a9fdc832ce57b7902ab9ff216c3faa/coverage-7.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96124be864b89395770c9a14652afcddbcdafb99466f53a9281c51d1466fb741", size = 257046, upload-time = "2025-07-24T16:52:27.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/95/577dc757c01f493a1951157475dd44561c82084387f12635974fb62e848c/coverage-7.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aad222e841f94b42bd1d6be71737fade66943853f0807cf87887c88f70883a2a", size = 257946, upload-time = "2025-07-24T16:52:29.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/5a/14b1be12e3a71fcf4031464ae285dab7df0939976236d0462c4c5382d317/coverage-7.10.0-cp314-cp314t-win32.whl", hash = "sha256:0eed5354d28caa5c8ad60e07e938f253e4b2810ea7dd56784339b6ce98b6f104", size = 218602, upload-time = "2025-07-24T16:52:32.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/8d/c32890c0f4f7f71b8d4a1074ef8e9ef28e9b9c2f9fd0e2896f2cc32593bf/coverage-7.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3da35f9980058acb960b2644527cc3911f1e00f94d309d704b309fa984029109", size = 219720, upload-time = "2025-07-24T16:52:34.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/f7/e5cc13338aa5e2780b6226fb50e9bd8f3f88da85a4b2951447b4b51109a4/coverage-7.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cb9e138dfa8a4b5c52c92a537651e2ca4f2ca48d8cb1bc01a2cbe7a5773c2426", size = 217374, upload-time = "2025-07-24T16:52:36.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/fb/ace937cb8faf4d723bfc6058fee39b6756d888cf7524559885e437d06d71/coverage-7.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cf283ec9c6878826291b17442eb5c32d3d252dc77d25e082b460b2d2ea67ba3c", size = 214811, upload-time = "2025-07-24T16:52:38.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/76/cbacf622916d4d3e1c5dbe07cacfdf19c80dfab9e5f65fa62d8fa0dbab31/coverage-7.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a83488c9fc6fff487f2ab551f9b64c70672357b8949f0951b0cd778b3ed8165", size = 215190, upload-time = "2025-07-24T16:52:40.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/24/794bebf18d9b6eb83defcc33b54c3af9ae781d2584aa07539631de2a4975/coverage-7.10.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b86df3a7494d12338c11e59f210a0498d6109bbc3a4037f44de517ebb30a9c6b", size = 241262, upload-time = "2025-07-24T16:52:42.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/49/674dfe9a00de71576d21825fb4c608db18ad69bec3e1184bf0b4d6e440c0/coverage-7.10.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6de9b460809e5e4787b742e786a36ae2346a53982e2be317cdcb7a33c56412fb", size = 243159, upload-time = "2025-07-24T16:52:44.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/0c/ff37bcbae61f0e7783a2b58019e757e368754819f24428beebb31a9589e9/coverage-7.10.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de5ef8a5954d63fa26a6aaa4600e48f885ce70fe495e8fce2c43aa9241fc9434", size = 244727, upload-time = "2025-07-24T16:52:46.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/d6/a42496f920770374a4116ccd01349d112e01969aeb03ba6eb3af74d5b7a0/coverage-7.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f178fe5e96f1e057527d5d0b20ab76b8616e0410169c33716cc226118eaf2c4f", size = 242662, upload-time = "2025-07-24T16:52:49.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f0/518341fbed44ada9660d92bb7001d848d6901d606f157d1d9009b36bfe1b/coverage-7.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4a38c42f0182a012fa9ec25bc6057e51114c1ba125be304f3f776d6d283cb303", size = 240896, upload-time = "2025-07-24T16:52:51.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/08/fbe01e9a7394e11215ec3c67d51c66947abb4a02c9076cd04e8ccd454fa5/coverage-7.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bf09beb5c1785cb36aad042455c0afab561399b74bb8cdaf6e82b7d77322df99", size = 241848, upload-time = "2025-07-24T16:52:53.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/d4/edf9d75080254d969e2a8c8b4f4a5391865a3097de493a2ad3c938c8c9d3/coverage-7.10.0-cp39-cp39-win32.whl", hash = "sha256:cb8dfbb5d3016cb8d1940444c0c69b40cdc6c8bde724b07716ee5ea47b5273c6", size = 217320, upload-time = "2025-07-24T16:52:55.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/bb/4ffaec3b62fa24faf4c462cbdb0145a395f532aacc85f2e51a571d54a74f/coverage-7.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:58ff22653cd93d563110d1ff2aef958f5f21be9e917762f8124d0e36f80f172a", size = 218215, upload-time = "2025-07-24T16:52:57.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/df/7c34bada8ace39f688b3bd5bc411459a20a3204ccb0984c90169a80a9366/coverage-7.10.0-py3-none-any.whl", hash = "sha256:310a786330bb0463775c21d68e26e79973839b66d29e065c5787122b8dd4489f", size = 206777, upload-time = "2025-07-24T16:52:59.009Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
toml = [
|
||||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openfeature-sdk"
|
||||
version = "0.8.1"
|
||||
source = { editable = "." }
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "behave" },
|
||||
{ name = "coverage", extra = ["toml"] },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "behave" },
|
||||
{ name = "coverage", extras = ["toml"], specifier = ">=6.5" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse"
|
||||
version = "1.20.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-type"
|
||||
version = "0.6.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "parse" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/e9/a3b2ae5f8a852542788ac1f1865dcea0c549cc40af243f42cabfa0acf24d/parse_type-0.6.4.tar.gz", hash = "sha256:5e1ec10440b000c3f818006033372939e693a9ec0176f446d9303e4db88489a6", size = 96480, upload-time = "2024-10-03T11:51:00.353Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/b3/f6cc950042bfdbe98672e7c834d930f85920fb7d3359f59096e8d2799617/parse_type-0.6.4-py2.py3-none-any.whl", hash = "sha256:83d41144a82d6b8541127bf212dd76c7f01baff680b498ce8a4d052a7a5bce4c", size = 27442, upload-time = "2024-10-03T11:50:58.519Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
{ name = "identify" },
|
||||
{ name = "nodeenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pytest" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.32.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970, upload-time = "2025-07-21T04:09:50.985Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" },
|
||||
]
|
Loading…
Reference in New Issue