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.
@@ -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,5 @@
1
+ """OpenFeature provider for e-volv Launch flags (e-volv-openfeature)."""
2
+
3
+ from evolve_openfeature.provider import EvolveProvider, to_evolve_context
4
+
5
+ __all__ = ["EvolveProvider", "to_evolve_context"]
@@ -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
+ ]