e-volv-openfeature 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- e_volv_openfeature-0.1.0/.gitignore +166 -0
- e_volv_openfeature-0.1.0/PKG-INFO +54 -0
- e_volv_openfeature-0.1.0/README.md +42 -0
- e_volv_openfeature-0.1.0/evolve_openfeature/__init__.py +5 -0
- e_volv_openfeature-0.1.0/evolve_openfeature/provider.py +141 -0
- e_volv_openfeature-0.1.0/evolve_openfeature/py.typed +0 -0
- e_volv_openfeature-0.1.0/pyproject.toml +53 -0
- e_volv_openfeature-0.1.0/tests/test_provider.py +135 -0
- e_volv_openfeature-0.1.0/uv.lock +266 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# compiled output
|
|
4
|
+
dist
|
|
5
|
+
tmp
|
|
6
|
+
out-tsc
|
|
7
|
+
|
|
8
|
+
# dependencies
|
|
9
|
+
node_modules
|
|
10
|
+
|
|
11
|
+
# IDEs and editors
|
|
12
|
+
/.idea
|
|
13
|
+
.project
|
|
14
|
+
.classpath
|
|
15
|
+
.c9/
|
|
16
|
+
*.launch
|
|
17
|
+
.settings/
|
|
18
|
+
*.sublime-workspace
|
|
19
|
+
|
|
20
|
+
# IDE - VSCode
|
|
21
|
+
.vscode/*
|
|
22
|
+
!.vscode/settings.json
|
|
23
|
+
!.vscode/tasks.json
|
|
24
|
+
!.vscode/launch.json
|
|
25
|
+
!.vscode/extensions.json
|
|
26
|
+
|
|
27
|
+
# misc
|
|
28
|
+
/.sass-cache
|
|
29
|
+
/connect.lock
|
|
30
|
+
/coverage
|
|
31
|
+
/libpeerconnection.log
|
|
32
|
+
npm-debug.log
|
|
33
|
+
yarn-error.log
|
|
34
|
+
testem.log
|
|
35
|
+
/typings
|
|
36
|
+
|
|
37
|
+
# System Files
|
|
38
|
+
.DS_Store
|
|
39
|
+
Thumbs.db
|
|
40
|
+
|
|
41
|
+
.nx/cache
|
|
42
|
+
.nx/workspace-data
|
|
43
|
+
.cursor/rules/nx-rules.mdc
|
|
44
|
+
.github/instructions/nx.instructions.md
|
|
45
|
+
|
|
46
|
+
.claude/worktrees
|
|
47
|
+
.claude/settings.local.json
|
|
48
|
+
.nx/polygraph
|
|
49
|
+
|
|
50
|
+
# Next.js
|
|
51
|
+
.next
|
|
52
|
+
out
|
|
53
|
+
# Terraform
|
|
54
|
+
infra/.terraform/
|
|
55
|
+
infra/*.tfstate
|
|
56
|
+
infra/*.tfstate.backup
|
|
57
|
+
# Terraform writes rollback copies as terraform.tfstate.<epoch>.backup, which
|
|
58
|
+
# the pattern above does not match — the timestamp sits before `.backup`. One
|
|
59
|
+
# such file was sitting untracked on 2026-08-09 holding 68 plaintext
|
|
60
|
+
# secret_data values (Surreal password, JWT secret, vault key, Stripe live key,
|
|
61
|
+
# GitHub App private key), fully stageable by `git add -A`.
|
|
62
|
+
infra/*.tfstate.*.backup
|
|
63
|
+
infra/terraform.tfvars
|
|
64
|
+
# Any sibling of the real tfvars — .bak, .orig, .save, dated copies. The exact
|
|
65
|
+
# path above does not match them, so an editor backup or a hand-rolled copy of
|
|
66
|
+
# a file holding every production secret lands in `git status` as untracked and
|
|
67
|
+
# would be staged by `git add -A`. Nearly happened on 2026-08-09.
|
|
68
|
+
infra/terraform.tfvars.*
|
|
69
|
+
!infra/terraform.tfvars.example
|
|
70
|
+
infra/.terraform.lock.hcl
|
|
71
|
+
|
|
72
|
+
# Environment files — never commit; contain live credentials
|
|
73
|
+
.env
|
|
74
|
+
.env.local
|
|
75
|
+
|
|
76
|
+
# Untracked 2026-08-07: this working copy accumulates real credential values as
|
|
77
|
+
# the remediation gates are worked through, so it cannot be a tracked file.
|
|
78
|
+
# The sanitised instructions live in docs/GATE-2-CREDENTIALS.md, which stays
|
|
79
|
+
# tracked and must never carry a real value.
|
|
80
|
+
docs/PRODUCTION-FIX-PLAN.md
|
|
81
|
+
.env.*.local
|
|
82
|
+
apps/*/.env
|
|
83
|
+
apps/*/.env.local
|
|
84
|
+
|
|
85
|
+
# Python bytecode caches
|
|
86
|
+
__pycache__/
|
|
87
|
+
*.pyc
|
|
88
|
+
|
|
89
|
+
# Nx/TS incremental build info
|
|
90
|
+
apps/evolve-backend/tsconfig.tsbuildinfo
|
|
91
|
+
|
|
92
|
+
# Test output (nx python targets write here)
|
|
93
|
+
reports/
|
|
94
|
+
|
|
95
|
+
# Terraform plan files embed sensitive variable values — never commit them.
|
|
96
|
+
# `-out=` takes an arbitrary name, so the two patterns below only catch plans a
|
|
97
|
+
# person happened to call "tfplan". Files named tfdom/tfgh/tfpost were found
|
|
98
|
+
# untracked on 2026-08-09. Plans are zip archives, so a name-based rule is the
|
|
99
|
+
# only defence — hence the explicit directory below for anything ad hoc.
|
|
100
|
+
infra/tfplan
|
|
101
|
+
infra/*.tfplan
|
|
102
|
+
infra/tf-plans/
|
|
103
|
+
|
|
104
|
+
# Playwright MCP writes browser console logs, traces and screenshots here when
|
|
105
|
+
# an agent drives the app. Session artefacts, not source — and they capture
|
|
106
|
+
# whatever the logged-in page happened to print, so they do not belong in git.
|
|
107
|
+
.playwright-mcp/
|
|
108
|
+
|
|
109
|
+
# Coverage artefacts — rewritten by every test run, never useful in a diff.
|
|
110
|
+
apps/evolve-ai/.coverage
|
|
111
|
+
test-output
|
|
112
|
+
|
|
113
|
+
# E2E build/output artefacts
|
|
114
|
+
apps/*-e2e/out-tsc/
|
|
115
|
+
apps/*-e2e/test-output/
|
|
116
|
+
playwright-report/
|
|
117
|
+
test-results/
|
|
118
|
+
|
|
119
|
+
# Memtrace (local code index — machine-local, never committed)
|
|
120
|
+
.memdb/
|
|
121
|
+
.memtrace/
|
|
122
|
+
.memtrace-workspace
|
|
123
|
+
|
|
124
|
+
# Local MCP server config — holds a Memtrace licence key.
|
|
125
|
+
.mcp.json
|
|
126
|
+
|
|
127
|
+
# Written by the Firebase Auth emulator that docs/E2E-TESTING.md tells you to start.
|
|
128
|
+
firebase-debug.log
|
|
129
|
+
firestore-debug.log
|
|
130
|
+
ui-debug.log
|
|
131
|
+
|
|
132
|
+
# TypeScript incremental build state (packages/logs-js writes one at its root).
|
|
133
|
+
*.tsbuildinfo
|
|
134
|
+
.logs-keys.json
|
|
135
|
+
.logs-keys-mspeed.json
|
|
136
|
+
|
|
137
|
+
# Recording rig: a saved production session, equivalent to a password.
|
|
138
|
+
tools/record/.auth/
|
|
139
|
+
|
|
140
|
+
# Recording rig: raw .webm captures and crop manifests. Only the rendered
|
|
141
|
+
# mp4/jpg under public/media/tour is kept.
|
|
142
|
+
tools/record/out/
|
|
143
|
+
|
|
144
|
+
# Recording rig scratch probes.
|
|
145
|
+
tools/record/*.tmp.ts
|
|
146
|
+
|
|
147
|
+
# Launch film renders. The rig is source; the output is 30MB of mp4 that is
|
|
148
|
+
# rebuilt by `node tools/film/build.mjs` in about three minutes.
|
|
149
|
+
tools/film/out/
|
|
150
|
+
# Score stems are committed as FLAC; the wavs are working copies and the
|
|
151
|
+
# mastered score.wav is rebuilt by `node tools/film/master-audio.mjs`.
|
|
152
|
+
tools/film/audio/*.wav
|
|
153
|
+
# Waveform and spectrogram plots — analysis output, regenerated on demand.
|
|
154
|
+
tools/film/audio/*.png
|
|
155
|
+
# Locally installed toolchains (e.g. a Go distribution fetched for SDK work).
|
|
156
|
+
# Never committed; keeps nx project-graph discovery away from them.
|
|
157
|
+
.tools/
|
|
158
|
+
|
|
159
|
+
# Flutter build output (regenerated by flutter test/build).
|
|
160
|
+
packages/logs-flutter/**/build/
|
|
161
|
+
# Dart/Flutter tool state (regenerated by pub/flutter tooling).
|
|
162
|
+
packages/logs-flutter/**/.dart_tool/
|
|
163
|
+
# Library lockfiles: Dart convention for published packages is not to commit
|
|
164
|
+
# them; example apps keep theirs.
|
|
165
|
+
packages/logs-flutter/**/pubspec.lock
|
|
166
|
+
!packages/logs-flutter/**/example/pubspec.lock
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: e-volv-openfeature
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenFeature provider for e-volv Launch flags.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Pactify-Pty-Ltd/theevolve/tree/main/packages/openfeature-py
|
|
6
|
+
Project-URL: Source, https://github.com/Pactify-Pty-Ltd/theevolve
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: e-volv-logs>=0.2.0
|
|
10
|
+
Requires-Dist: openfeature-sdk>=0.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# e-volv-openfeature
|
|
14
|
+
|
|
15
|
+
OpenFeature provider for [e-volv Launch](https://github.com/Pactify-Pty-Ltd/theevolve) flags, on top of the `e-volv-logs` Python SDK.
|
|
16
|
+
|
|
17
|
+
Evaluation underneath stays synchronous and never raises (the e-volv Launch SDK contract); only `initialize` waits — and only because OpenFeature's provider handshake asked it to.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from openfeature import api
|
|
23
|
+
from openfeature.evaluation_context import EvaluationContext
|
|
24
|
+
|
|
25
|
+
from evolve_logs import LogsClient, FlagsOptions
|
|
26
|
+
from evolve_openfeature import EvolveProvider
|
|
27
|
+
|
|
28
|
+
logs = LogsClient(
|
|
29
|
+
key="evk_…", url="https://api.e-volv.io/api/public/v1/logs", flags=FlagsOptions()
|
|
30
|
+
)
|
|
31
|
+
api.set_provider(EvolveProvider(logs.flags))
|
|
32
|
+
|
|
33
|
+
client = api.get_client()
|
|
34
|
+
details = client.get_boolean_details(
|
|
35
|
+
"checkout.new",
|
|
36
|
+
False,
|
|
37
|
+
EvaluationContext(targeting_key="u_1", attributes={"plan": "pro"}),
|
|
38
|
+
)
|
|
39
|
+
details.value, details.variant, details.flag_metadata["evolveReason"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Reason and error mapping
|
|
43
|
+
|
|
44
|
+
| e-volv reason | OpenFeature `reason` | `error_code` |
|
|
45
|
+
| ------------------------ | -------------------- | ---------------- |
|
|
46
|
+
| `RULE:*`, `TARGET_MATCH` | `TARGETING_MATCH` | — |
|
|
47
|
+
| `ROLLOUT` | `SPLIT` | — |
|
|
48
|
+
| `OFF`, `KILLED` | `DISABLED` | — |
|
|
49
|
+
| `FLAG_NOT_FOUND` | `ERROR` | `FLAG_NOT_FOUND` |
|
|
50
|
+
| `TYPE_MISMATCH` | `ERROR` | `TYPE_MISMATCH` |
|
|
51
|
+
| `ERROR` | `ERROR` | `GENERAL` |
|
|
52
|
+
| anything else | `DEFAULT` | — |
|
|
53
|
+
|
|
54
|
+
The native e-volv reason is always preserved as `flag_metadata["evolveReason"]`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# e-volv-openfeature
|
|
2
|
+
|
|
3
|
+
OpenFeature provider for [e-volv Launch](https://github.com/Pactify-Pty-Ltd/theevolve) flags, on top of the `e-volv-logs` Python SDK.
|
|
4
|
+
|
|
5
|
+
Evaluation underneath stays synchronous and never raises (the e-volv Launch SDK contract); only `initialize` waits — and only because OpenFeature's provider handshake asked it to.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from openfeature import api
|
|
11
|
+
from openfeature.evaluation_context import EvaluationContext
|
|
12
|
+
|
|
13
|
+
from evolve_logs import LogsClient, FlagsOptions
|
|
14
|
+
from evolve_openfeature import EvolveProvider
|
|
15
|
+
|
|
16
|
+
logs = LogsClient(
|
|
17
|
+
key="evk_…", url="https://api.e-volv.io/api/public/v1/logs", flags=FlagsOptions()
|
|
18
|
+
)
|
|
19
|
+
api.set_provider(EvolveProvider(logs.flags))
|
|
20
|
+
|
|
21
|
+
client = api.get_client()
|
|
22
|
+
details = client.get_boolean_details(
|
|
23
|
+
"checkout.new",
|
|
24
|
+
False,
|
|
25
|
+
EvaluationContext(targeting_key="u_1", attributes={"plan": "pro"}),
|
|
26
|
+
)
|
|
27
|
+
details.value, details.variant, details.flag_metadata["evolveReason"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Reason and error mapping
|
|
31
|
+
|
|
32
|
+
| e-volv reason | OpenFeature `reason` | `error_code` |
|
|
33
|
+
| ------------------------ | -------------------- | ---------------- |
|
|
34
|
+
| `RULE:*`, `TARGET_MATCH` | `TARGETING_MATCH` | — |
|
|
35
|
+
| `ROLLOUT` | `SPLIT` | — |
|
|
36
|
+
| `OFF`, `KILLED` | `DISABLED` | — |
|
|
37
|
+
| `FLAG_NOT_FOUND` | `ERROR` | `FLAG_NOT_FOUND` |
|
|
38
|
+
| `TYPE_MISMATCH` | `ERROR` | `TYPE_MISMATCH` |
|
|
39
|
+
| `ERROR` | `ERROR` | `GENERAL` |
|
|
40
|
+
| anything else | `DEFAULT` | — |
|
|
41
|
+
|
|
42
|
+
The native e-volv reason is always preserved as `flag_metadata["evolveReason"]`.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""e-volv Launch as an OpenFeature provider.
|
|
2
|
+
|
|
3
|
+
The evaluation underneath stays synchronous (docs/LAUNCH-SDK.md §3); only
|
|
4
|
+
`initialize` waits, and only because the caller opted into OpenFeature's
|
|
5
|
+
provider-readiness handshake.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from evolve_logs.flags import Evaluation
|
|
11
|
+
from evolve_logs.flags.kernel.jsvalues import js_type
|
|
12
|
+
from openfeature.evaluation_context import EvaluationContext
|
|
13
|
+
from openfeature.exception import ErrorCode
|
|
14
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
|
15
|
+
from openfeature.provider import AbstractProvider, Metadata
|
|
16
|
+
|
|
17
|
+
PROVIDER_NAME = "e-volv Launch"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def to_evolve_context(context: EvaluationContext | None) -> dict:
|
|
21
|
+
"""{targetingKey, ...attributes}; absent key omitted, never None."""
|
|
22
|
+
if context is None:
|
|
23
|
+
return {}
|
|
24
|
+
evolve: dict = {"targetingKey": context.targeting_key}
|
|
25
|
+
evolve.update(context.attributes or {})
|
|
26
|
+
if evolve["targetingKey"] is None:
|
|
27
|
+
del evolve["targetingKey"]
|
|
28
|
+
return evolve
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def to_resolution_details(detail: Evaluation) -> FlagResolutionDetails:
|
|
32
|
+
"""Map the kernel's native reasons onto OpenFeature's (S1 Task 10's table)."""
|
|
33
|
+
reason = detail.reason
|
|
34
|
+
base = {
|
|
35
|
+
"value": detail.value,
|
|
36
|
+
"variant": detail.variant,
|
|
37
|
+
"flag_metadata": {"evolveReason": reason},
|
|
38
|
+
}
|
|
39
|
+
if reason.startswith("RULE:") or reason == "TARGET_MATCH":
|
|
40
|
+
return FlagResolutionDetails(reason=Reason.TARGETING_MATCH, **base)
|
|
41
|
+
if reason == "ROLLOUT":
|
|
42
|
+
return FlagResolutionDetails(reason=Reason.SPLIT, **base)
|
|
43
|
+
if reason in ("OFF", "KILLED"):
|
|
44
|
+
return FlagResolutionDetails(reason=Reason.DISABLED, **base)
|
|
45
|
+
if reason == "FLAG_NOT_FOUND":
|
|
46
|
+
return FlagResolutionDetails(
|
|
47
|
+
reason=Reason.ERROR, error_code=ErrorCode.FLAG_NOT_FOUND, **base
|
|
48
|
+
)
|
|
49
|
+
if reason == "TYPE_MISMATCH":
|
|
50
|
+
return FlagResolutionDetails(
|
|
51
|
+
reason=Reason.ERROR, error_code=ErrorCode.TYPE_MISMATCH, **base
|
|
52
|
+
)
|
|
53
|
+
if reason == "ERROR":
|
|
54
|
+
return FlagResolutionDetails(
|
|
55
|
+
reason=Reason.ERROR, error_code=ErrorCode.GENERAL, **base
|
|
56
|
+
)
|
|
57
|
+
return FlagResolutionDetails(reason=Reason.DEFAULT, **base)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class EvolveProvider(AbstractProvider):
|
|
61
|
+
"""Wraps the flags handle of an e-volv-logs LogsClient."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, flags) -> None:
|
|
64
|
+
self._flags = flags
|
|
65
|
+
|
|
66
|
+
def get_metadata(self) -> Metadata:
|
|
67
|
+
return Metadata(name=PROVIDER_NAME)
|
|
68
|
+
|
|
69
|
+
def initialize(self, evaluation_context: EvaluationContext | None) -> None:
|
|
70
|
+
self._flags.ready(5.0)
|
|
71
|
+
|
|
72
|
+
def shutdown(self) -> None:
|
|
73
|
+
"""The LogsClient owns delivery; shutting OpenFeature down must not
|
|
74
|
+
stop telemetry."""
|
|
75
|
+
|
|
76
|
+
def resolve_boolean_details(
|
|
77
|
+
self,
|
|
78
|
+
flag_key: str,
|
|
79
|
+
default_value: bool,
|
|
80
|
+
evaluation_context: EvaluationContext | None = None,
|
|
81
|
+
) -> FlagResolutionDetails[bool]:
|
|
82
|
+
return self._typed(
|
|
83
|
+
flag_key, default_value, evaluation_context, expected="boolean"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def resolve_string_details(
|
|
87
|
+
self,
|
|
88
|
+
flag_key: str,
|
|
89
|
+
default_value: str,
|
|
90
|
+
evaluation_context: EvaluationContext | None = None,
|
|
91
|
+
) -> FlagResolutionDetails[str]:
|
|
92
|
+
return self._typed(
|
|
93
|
+
flag_key, default_value, evaluation_context, expected="string"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def resolve_integer_details(
|
|
97
|
+
self,
|
|
98
|
+
flag_key: str,
|
|
99
|
+
default_value: int,
|
|
100
|
+
evaluation_context: EvaluationContext | None = None,
|
|
101
|
+
) -> FlagResolutionDetails[int]:
|
|
102
|
+
return self._typed(
|
|
103
|
+
flag_key, default_value, evaluation_context, expected="number"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def resolve_float_details(
|
|
107
|
+
self,
|
|
108
|
+
flag_key: str,
|
|
109
|
+
default_value: float,
|
|
110
|
+
evaluation_context: EvaluationContext | None = None,
|
|
111
|
+
) -> FlagResolutionDetails[float]:
|
|
112
|
+
return self._typed(
|
|
113
|
+
flag_key, default_value, evaluation_context, expected="number"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def resolve_object_details(
|
|
117
|
+
self,
|
|
118
|
+
flag_key: str,
|
|
119
|
+
default_value,
|
|
120
|
+
evaluation_context: EvaluationContext | None = None,
|
|
121
|
+
) -> FlagResolutionDetails:
|
|
122
|
+
return self._typed(flag_key, default_value, evaluation_context, expected=None)
|
|
123
|
+
|
|
124
|
+
def _typed(
|
|
125
|
+
self,
|
|
126
|
+
flag_key: str,
|
|
127
|
+
default_value,
|
|
128
|
+
evaluation_context: EvaluationContext | None,
|
|
129
|
+
*,
|
|
130
|
+
expected: str | None,
|
|
131
|
+
) -> FlagResolutionDetails:
|
|
132
|
+
detail = self._flags.detail(
|
|
133
|
+
flag_key, default_value, to_evolve_context(evaluation_context)
|
|
134
|
+
)
|
|
135
|
+
if (
|
|
136
|
+
expected is not None
|
|
137
|
+
and detail.reason not in ("FLAG_NOT_FOUND", "ERROR")
|
|
138
|
+
and js_type(detail.value) != expected
|
|
139
|
+
):
|
|
140
|
+
detail = Evaluation(default_value, None, "TYPE_MISMATCH")
|
|
141
|
+
return to_resolution_details(detail)
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "e-volv-openfeature"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "OpenFeature provider for e-volv Launch flags."
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
dependencies = [
|
|
9
|
+
"e-volv-logs>=0.2.0",
|
|
10
|
+
"openfeature-sdk>=0.8",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Homepage = "https://github.com/Pactify-Pty-Ltd/theevolve/tree/main/packages/openfeature-py"
|
|
15
|
+
Source = "https://github.com/Pactify-Pty-Ltd/theevolve"
|
|
16
|
+
|
|
17
|
+
[tool.hatch.build.targets.wheel]
|
|
18
|
+
packages = ["evolve_openfeature"]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=8.3.4",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# Local development resolves e-volv-logs from the sibling checkout; the
|
|
30
|
+
# published metadata keeps the registry requirement above.
|
|
31
|
+
[tool.uv.sources]
|
|
32
|
+
e-volv-logs = { path = "../logs-py" }
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 88
|
|
36
|
+
indent-width = 4
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = [
|
|
40
|
+
# pycodestyle
|
|
41
|
+
"E",
|
|
42
|
+
# Pyflakes
|
|
43
|
+
"F",
|
|
44
|
+
# pyupgrade
|
|
45
|
+
"UP",
|
|
46
|
+
# flake8-bugbear
|
|
47
|
+
"B",
|
|
48
|
+
# flake8-simplify
|
|
49
|
+
"SIM",
|
|
50
|
+
# isort
|
|
51
|
+
"I",
|
|
52
|
+
]
|
|
53
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""EvolveProvider against the kernel fixture and the S1 reason mapping table."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from evolve_logs import LogsClient
|
|
8
|
+
from evolve_logs.flags import FlagsOptions
|
|
9
|
+
from openfeature import api
|
|
10
|
+
from openfeature.evaluation_context import EvaluationContext
|
|
11
|
+
from openfeature.exception import ErrorCode
|
|
12
|
+
from openfeature.flag_evaluation import Reason
|
|
13
|
+
|
|
14
|
+
from evolve_openfeature import EvolveProvider, to_evolve_context
|
|
15
|
+
|
|
16
|
+
FIXTURE = json.loads(
|
|
17
|
+
(Path(__file__).parents[2] / "flags-kernel" / "fixture.json").read_text()
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
EXPECTED_REASONS = {
|
|
21
|
+
"KILLED": (Reason.DISABLED, None),
|
|
22
|
+
"OFF": (Reason.DISABLED, None),
|
|
23
|
+
"PREREQUISITE_FAILED": (Reason.DEFAULT, None),
|
|
24
|
+
"TARGET_MATCH": (Reason.TARGETING_MATCH, None),
|
|
25
|
+
"ROLLOUT": (Reason.SPLIT, None),
|
|
26
|
+
"DEFAULT": (Reason.DEFAULT, None),
|
|
27
|
+
"FLAG_NOT_FOUND": (Reason.ERROR, ErrorCode.FLAG_NOT_FOUND),
|
|
28
|
+
"ERROR": (Reason.ERROR, ErrorCode.GENERAL),
|
|
29
|
+
"RULE:0": (Reason.TARGETING_MATCH, None),
|
|
30
|
+
"RULE:1": (Reason.TARGETING_MATCH, None),
|
|
31
|
+
"RULE:2": (Reason.TARGETING_MATCH, None),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
ERROR_REASONS = {"FLAG_NOT_FOUND", "TYPE_MISMATCH", "ERROR"}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture()
|
|
39
|
+
def client():
|
|
40
|
+
client = LogsClient(
|
|
41
|
+
key="evk_test_provider",
|
|
42
|
+
capture_excepthook=False,
|
|
43
|
+
flags=FlagsOptions(
|
|
44
|
+
mode="offline",
|
|
45
|
+
cache=False,
|
|
46
|
+
bootstrap=FIXTURE["ruleset"],
|
|
47
|
+
exposures_enabled=False,
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
yield client
|
|
51
|
+
client.close()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_reason_table_complete():
|
|
55
|
+
reasons = {case["expect"]["reason"] for case in FIXTURE["cases"]}
|
|
56
|
+
assert reasons == set(EXPECTED_REASONS)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _getter_for(case):
|
|
60
|
+
"""The Python SDK type-checks resolved values client-side, so each case
|
|
61
|
+
goes through the typed getter matching the flag (or its fallback)."""
|
|
62
|
+
definition = (FIXTURE["ruleset"]["flags"].get(case["flag"]) or {}).get("definition")
|
|
63
|
+
if definition:
|
|
64
|
+
flag_type = definition["type"]
|
|
65
|
+
else:
|
|
66
|
+
fallback = case["fallback"]
|
|
67
|
+
flag_type = (
|
|
68
|
+
"boolean"
|
|
69
|
+
if isinstance(fallback, bool)
|
|
70
|
+
else "number"
|
|
71
|
+
if isinstance(fallback, (int, float))
|
|
72
|
+
else "json"
|
|
73
|
+
if isinstance(fallback, (dict, list))
|
|
74
|
+
else "string"
|
|
75
|
+
)
|
|
76
|
+
return {
|
|
77
|
+
"boolean": "get_boolean_details",
|
|
78
|
+
"string": "get_string_details",
|
|
79
|
+
"number": "get_integer_details",
|
|
80
|
+
"json": "get_object_details",
|
|
81
|
+
}[flag_type]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_every_fixture_case_through_openfeature(client):
|
|
85
|
+
api.set_provider(EvolveProvider(client.flags))
|
|
86
|
+
of_client = api.get_client()
|
|
87
|
+
for case in FIXTURE["cases"]:
|
|
88
|
+
ctx = case["context"]
|
|
89
|
+
targeting_key = ctx.get("targetingKey") or ctx.get("key")
|
|
90
|
+
get_details = getattr(of_client, _getter_for(case))
|
|
91
|
+
details = get_details(
|
|
92
|
+
case["flag"],
|
|
93
|
+
case["fallback"],
|
|
94
|
+
EvaluationContext(targeting_key=targeting_key, attributes=ctx),
|
|
95
|
+
)
|
|
96
|
+
if case["expect"]["reason"] in ERROR_REASONS:
|
|
97
|
+
# The OpenFeature client substitutes the caller's default whenever
|
|
98
|
+
# a resolution carries an errorCode — the portable "error →
|
|
99
|
+
# default" contract (S1 node spec verified the same against
|
|
100
|
+
# @openfeature/server-sdk). The Python client keeps the variant.
|
|
101
|
+
assert details.value == case["fallback"], case["name"]
|
|
102
|
+
else:
|
|
103
|
+
assert details.value == case["expect"]["value"], case["name"]
|
|
104
|
+
assert details.variant == case["expect"]["variant"], case["name"]
|
|
105
|
+
assert details.flag_metadata["evolveReason"] == case["expect"]["reason"], case[
|
|
106
|
+
"name"
|
|
107
|
+
]
|
|
108
|
+
expected_reason, expected_error = EXPECTED_REASONS[case["expect"]["reason"]]
|
|
109
|
+
assert details.reason == expected_reason, case["name"]
|
|
110
|
+
assert details.error_code == expected_error, case["name"]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_typed_mismatch_reports_error_code(client):
|
|
114
|
+
api.set_provider(EvolveProvider(client.flags))
|
|
115
|
+
of_client = api.get_client()
|
|
116
|
+
ctx = {"targetingKey": "u_1", "email": "dana@acme.test"}
|
|
117
|
+
details = of_client.get_string_details(
|
|
118
|
+
"checkout.new",
|
|
119
|
+
"fallback",
|
|
120
|
+
EvaluationContext(targeting_key="u_1", attributes=ctx),
|
|
121
|
+
)
|
|
122
|
+
assert details.value == "fallback"
|
|
123
|
+
assert details.reason == Reason.ERROR
|
|
124
|
+
assert details.error_code == ErrorCode.TYPE_MISMATCH
|
|
125
|
+
assert details.flag_metadata["evolveReason"] == "TYPE_MISMATCH"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_to_evolve_context_shape():
|
|
129
|
+
assert to_evolve_context(None) == {}
|
|
130
|
+
assert to_evolve_context(EvaluationContext(targeting_key="u1")) == {
|
|
131
|
+
"targetingKey": "u1"
|
|
132
|
+
}
|
|
133
|
+
assert to_evolve_context(
|
|
134
|
+
EvaluationContext(targeting_key="u1", attributes={"plan": "pro"})
|
|
135
|
+
) == {"targetingKey": "u1", "plan": "pro"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.10"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "anyio"
|
|
7
|
+
version = "4.15.1"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
11
|
+
{ name = "idna" },
|
|
12
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.15'" },
|
|
13
|
+
]
|
|
14
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
|
|
15
|
+
wheels = [
|
|
16
|
+
{ url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "certifi"
|
|
21
|
+
version = "2026.7.22"
|
|
22
|
+
source = { registry = "https://pypi.org/simple" }
|
|
23
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
|
|
24
|
+
wheels = [
|
|
25
|
+
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[package]]
|
|
29
|
+
name = "colorama"
|
|
30
|
+
version = "0.4.6"
|
|
31
|
+
source = { registry = "https://pypi.org/simple" }
|
|
32
|
+
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" }
|
|
33
|
+
wheels = [
|
|
34
|
+
{ 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" },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[[package]]
|
|
38
|
+
name = "e-volv-logs"
|
|
39
|
+
version = "0.2.0"
|
|
40
|
+
source = { directory = "../logs-py" }
|
|
41
|
+
dependencies = [
|
|
42
|
+
{ name = "httpx" },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[package.metadata]
|
|
46
|
+
requires-dist = [
|
|
47
|
+
{ name = "httpx", specifier = ">=0.28.0" },
|
|
48
|
+
{ name = "langchain-core", marker = "extra == 'langchain'", specifier = ">=0.3" },
|
|
49
|
+
]
|
|
50
|
+
provides-extras = ["langchain"]
|
|
51
|
+
|
|
52
|
+
[package.metadata.requires-dev]
|
|
53
|
+
dev = [
|
|
54
|
+
{ name = "fastapi", specifier = ">=0.115.0" },
|
|
55
|
+
{ name = "pytest", specifier = ">=8.3.4" },
|
|
56
|
+
{ name = "pytest-asyncio", specifier = ">=0.25.0" },
|
|
57
|
+
{ name = "requests", specifier = ">=2.32.0" },
|
|
58
|
+
{ name = "ruff", specifier = ">=0.9" },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "e-volv-openfeature"
|
|
63
|
+
version = "0.1.0"
|
|
64
|
+
source = { editable = "." }
|
|
65
|
+
dependencies = [
|
|
66
|
+
{ name = "e-volv-logs" },
|
|
67
|
+
{ name = "openfeature-sdk" },
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[package.dev-dependencies]
|
|
71
|
+
dev = [
|
|
72
|
+
{ name = "pytest" },
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[package.metadata]
|
|
76
|
+
requires-dist = [
|
|
77
|
+
{ name = "e-volv-logs", directory = "../logs-py" },
|
|
78
|
+
{ name = "openfeature-sdk", specifier = ">=0.8" },
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[package.metadata.requires-dev]
|
|
82
|
+
dev = [{ name = "pytest", specifier = ">=8.3.4" }]
|
|
83
|
+
|
|
84
|
+
[[package]]
|
|
85
|
+
name = "exceptiongroup"
|
|
86
|
+
version = "1.3.1"
|
|
87
|
+
source = { registry = "https://pypi.org/simple" }
|
|
88
|
+
dependencies = [
|
|
89
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
90
|
+
]
|
|
91
|
+
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
|
92
|
+
wheels = [
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[[package]]
|
|
97
|
+
name = "h11"
|
|
98
|
+
version = "0.16.0"
|
|
99
|
+
source = { registry = "https://pypi.org/simple" }
|
|
100
|
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
|
101
|
+
wheels = [
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[[package]]
|
|
106
|
+
name = "httpcore"
|
|
107
|
+
version = "1.0.9"
|
|
108
|
+
source = { registry = "https://pypi.org/simple" }
|
|
109
|
+
dependencies = [
|
|
110
|
+
{ name = "certifi" },
|
|
111
|
+
{ name = "h11" },
|
|
112
|
+
]
|
|
113
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
|
114
|
+
wheels = [
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
[[package]]
|
|
119
|
+
name = "httpx"
|
|
120
|
+
version = "0.28.1"
|
|
121
|
+
source = { registry = "https://pypi.org/simple" }
|
|
122
|
+
dependencies = [
|
|
123
|
+
{ name = "anyio" },
|
|
124
|
+
{ name = "certifi" },
|
|
125
|
+
{ name = "httpcore" },
|
|
126
|
+
{ name = "idna" },
|
|
127
|
+
]
|
|
128
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
|
129
|
+
wheels = [
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[[package]]
|
|
134
|
+
name = "idna"
|
|
135
|
+
version = "3.19"
|
|
136
|
+
source = { registry = "https://pypi.org/simple" }
|
|
137
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
|
|
138
|
+
wheels = [
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[[package]]
|
|
143
|
+
name = "iniconfig"
|
|
144
|
+
version = "2.3.0"
|
|
145
|
+
source = { registry = "https://pypi.org/simple" }
|
|
146
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
147
|
+
wheels = [
|
|
148
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
[[package]]
|
|
152
|
+
name = "openfeature-sdk"
|
|
153
|
+
version = "0.10.0"
|
|
154
|
+
source = { registry = "https://pypi.org/simple" }
|
|
155
|
+
sdist = { url = "https://files.pythonhosted.org/packages/24/30/cfc684b7d8314398d476ae8ed515c10db99c4d7f950989db464b4ded12ce/openfeature_sdk-0.10.0.tar.gz", hash = "sha256:938c2540bdea4da3b01ef507517ee636f223a35abaaca845c5587e594151b052", size = 33516, upload-time = "2026-06-01T19:45:35.136Z" }
|
|
156
|
+
wheels = [
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/da/44/8a4f5225e930ff0d999fd43f5d743a4babeb6c7e76dddc00f0e118878ef3/openfeature_sdk-0.10.0-py3-none-any.whl", hash = "sha256:75497ea75d73f684eef509a25f79ad6386368862e050af80ab70a44ae49b33e4", size = 38941, upload-time = "2026-06-01T19:45:33.011Z" },
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
[[package]]
|
|
161
|
+
name = "packaging"
|
|
162
|
+
version = "26.3"
|
|
163
|
+
source = { registry = "https://pypi.org/simple" }
|
|
164
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
|
165
|
+
wheels = [
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
[[package]]
|
|
170
|
+
name = "pluggy"
|
|
171
|
+
version = "1.6.0"
|
|
172
|
+
source = { registry = "https://pypi.org/simple" }
|
|
173
|
+
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" }
|
|
174
|
+
wheels = [
|
|
175
|
+
{ 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" },
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
[[package]]
|
|
179
|
+
name = "pygments"
|
|
180
|
+
version = "2.21.0"
|
|
181
|
+
source = { registry = "https://pypi.org/simple" }
|
|
182
|
+
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
|
183
|
+
wheels = [
|
|
184
|
+
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
[[package]]
|
|
188
|
+
name = "pytest"
|
|
189
|
+
version = "9.1.1"
|
|
190
|
+
source = { registry = "https://pypi.org/simple" }
|
|
191
|
+
dependencies = [
|
|
192
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
193
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
194
|
+
{ name = "iniconfig" },
|
|
195
|
+
{ name = "packaging" },
|
|
196
|
+
{ name = "pluggy" },
|
|
197
|
+
{ name = "pygments" },
|
|
198
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
199
|
+
]
|
|
200
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
|
201
|
+
wheels = [
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
[[package]]
|
|
206
|
+
name = "tomli"
|
|
207
|
+
version = "2.4.1"
|
|
208
|
+
source = { registry = "https://pypi.org/simple" }
|
|
209
|
+
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
|
|
210
|
+
wheels = [
|
|
211
|
+
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
|
|
212
|
+
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
|
|
213
|
+
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
|
|
214
|
+
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
|
|
215
|
+
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
|
|
216
|
+
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
|
|
217
|
+
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
|
|
218
|
+
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
|
|
221
|
+
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
|
|
222
|
+
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
|
|
223
|
+
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
|
|
224
|
+
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
|
|
225
|
+
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
|
|
226
|
+
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
|
|
227
|
+
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
|
|
228
|
+
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
|
|
229
|
+
{ url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
|
|
230
|
+
{ url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
|
|
231
|
+
{ url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
|
|
236
|
+
{ url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
[[package]]
|
|
260
|
+
name = "typing-extensions"
|
|
261
|
+
version = "4.16.0"
|
|
262
|
+
source = { registry = "https://pypi.org/simple" }
|
|
263
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
|
264
|
+
wheels = [
|
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
|
266
|
+
]
|