flagsmith-flag-engine 10.2.0__tar.gz → 11.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.
- {flagsmith_flag_engine-10.2.0/flagsmith_flag_engine.egg-info → flagsmith_flag_engine-11.1.0}/PKG-INFO +4 -2
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/context/types.py +2 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/evaluator.py +201 -22
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0/flagsmith_flag_engine.egg-info}/PKG-INFO +4 -2
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/setup.py +3 -2
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/LICENSE.txt +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/README.md +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/__init__.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/context/__init__.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/context/mappers.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/engine.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/py.typed +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/result/__init__.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/result/types.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/__init__.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/constants.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/types.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/utils.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/__init__.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/hashing.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/json/__init__.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/json/encoders.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/semver.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/types.py +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flagsmith_flag_engine.egg-info/SOURCES.txt +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flagsmith_flag_engine.egg-info/dependency_links.txt +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flagsmith_flag_engine.egg-info/requires.txt +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flagsmith_flag_engine.egg-info/top_level.txt +0 -0
- {flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flagsmith-flag-engine
|
|
3
|
-
Version:
|
|
3
|
+
Version: 11.1.0
|
|
4
4
|
Summary: Flag engine for the Flagsmith API.
|
|
5
5
|
Home-page: https://github.com/Flagsmith/flagsmith-engine
|
|
6
6
|
Author: Flagsmith
|
|
@@ -8,11 +8,12 @@ Author-email: support@flagsmith.com
|
|
|
8
8
|
License: BSD3
|
|
9
9
|
Classifier: License :: OSI Approved :: BSD License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.10
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
18
|
License-File: LICENSE.txt
|
|
18
19
|
Requires-Dist: jsonpath-rfc9535<1,>=0.1.5
|
|
@@ -27,6 +28,7 @@ Dynamic: home-page
|
|
|
27
28
|
Dynamic: license
|
|
28
29
|
Dynamic: license-file
|
|
29
30
|
Dynamic: requires-dist
|
|
31
|
+
Dynamic: requires-python
|
|
30
32
|
Dynamic: summary
|
|
31
33
|
|
|
32
34
|
[](https://www.flagsmith.com/)
|
|
@@ -8,6 +8,7 @@ from typing import Any, Dict, Generic, List, Literal, Optional, Union
|
|
|
8
8
|
|
|
9
9
|
from typing_extensions import NotRequired, TypedDict
|
|
10
10
|
|
|
11
|
+
from flag_engine.result.types import FlagResult
|
|
11
12
|
from flag_engine.segments.types import (
|
|
12
13
|
ConditionOperator,
|
|
13
14
|
ContextValue,
|
|
@@ -79,3 +80,4 @@ class EvaluationContext(TypedDict, Generic[SegmentMetadataT, FeatureMetadataT]):
|
|
|
79
80
|
identity: NotRequired[Optional[IdentityContext]]
|
|
80
81
|
segments: NotRequired[Dict[str, SegmentContext[SegmentMetadataT, FeatureMetadataT]]]
|
|
81
82
|
features: NotRequired[Dict[str, FeatureContext[FeatureMetadataT]]]
|
|
83
|
+
flags: NotRequired[Dict[str, FlagResult[FeatureMetadataT]]]
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/evaluator.py
RENAMED
|
@@ -6,7 +6,7 @@ import re
|
|
|
6
6
|
import typing
|
|
7
7
|
import warnings
|
|
8
8
|
from contextlib import suppress
|
|
9
|
-
from functools import lru_cache, partial, wraps
|
|
9
|
+
from functools import cached_property, lru_cache, partial, wraps
|
|
10
10
|
|
|
11
11
|
import jsonpath_rfc9535
|
|
12
12
|
import semver
|
|
@@ -58,15 +58,66 @@ def get_evaluation_result(
|
|
|
58
58
|
:return: EvaluationResult containing the context, flags, and segments
|
|
59
59
|
"""
|
|
60
60
|
context = get_enriched_context(context)
|
|
61
|
-
|
|
61
|
+
|
|
62
|
+
resolved: _LazyFlags = _LazyFlags()
|
|
63
|
+
context = {**context, "flags": resolved}
|
|
64
|
+
resolved.bind(context)
|
|
65
|
+
|
|
66
|
+
segments, segment_overrides = evaluate_segments(context, resolved)
|
|
62
67
|
flags = evaluate_features(context, segment_overrides)
|
|
63
68
|
|
|
69
|
+
if resolved.used:
|
|
70
|
+
# Only reached when a segment condition read a flag. Those results take
|
|
71
|
+
# precedence: they were resolved with the cycle guard, unlike the
|
|
72
|
+
# single-pass recomputation above.
|
|
73
|
+
flags.update(resolved)
|
|
74
|
+
for feature_name in resolved.cyclic:
|
|
75
|
+
if (flag := flags.get(feature_name)) is not None:
|
|
76
|
+
flag["reason"] = CIRCULAR_DEPENDENCY_REASON
|
|
77
|
+
|
|
64
78
|
return {
|
|
65
79
|
"flags": flags,
|
|
66
80
|
"segments": segments,
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
|
|
84
|
+
class _LazyFlags(dict[str, FlagResult[typing.Any]]):
|
|
85
|
+
"""
|
|
86
|
+
The `$.flags` mapping, resolving a flag when a condition first reads it.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
_context: _EvaluationContextAnyMeta
|
|
90
|
+
|
|
91
|
+
#: Names of the flags found to be in a dependency cycle.
|
|
92
|
+
cyclic: set[str]
|
|
93
|
+
|
|
94
|
+
#: Set the first time a condition reads a flag. Distinguishes "no
|
|
95
|
+
#: dependency was ever consulted" from "one was, and resolved to nothing",
|
|
96
|
+
#: which an empty mapping cannot.
|
|
97
|
+
used: bool = False
|
|
98
|
+
|
|
99
|
+
#: Incremented whenever a cycle is cut. A segment whose own evaluation
|
|
100
|
+
#: increments it read a flag that could not be resolved, so its verdict
|
|
101
|
+
#: rests on that flag's absence and it must not match.
|
|
102
|
+
cycle_hits: int = 0
|
|
103
|
+
|
|
104
|
+
def bind(self, context: _EvaluationContextAnyMeta) -> None:
|
|
105
|
+
self._context = context
|
|
106
|
+
|
|
107
|
+
@cached_property
|
|
108
|
+
def _resolver(self) -> _DependencyResolver[typing.Any, typing.Any]:
|
|
109
|
+
return _DependencyResolver(self._context, self)
|
|
110
|
+
|
|
111
|
+
def __missing__(self, key: str) -> typing.Optional[FlagResult[typing.Any]]:
|
|
112
|
+
if not self.used:
|
|
113
|
+
# Both are only needed once a condition has read a flag, so a
|
|
114
|
+
# context whose segments read none allocates neither.
|
|
115
|
+
self.used = True
|
|
116
|
+
self.cyclic = set()
|
|
117
|
+
self._resolver.resolve_feature(key)
|
|
118
|
+
return self.get(key)
|
|
119
|
+
|
|
120
|
+
|
|
70
121
|
def get_enriched_context(
|
|
71
122
|
context: EvaluationContext[SegmentMetadataT, FeatureMetadataT],
|
|
72
123
|
) -> EvaluationContext[SegmentMetadataT, FeatureMetadataT]:
|
|
@@ -90,8 +141,25 @@ def get_enriched_context(
|
|
|
90
141
|
return context
|
|
91
142
|
|
|
92
143
|
|
|
144
|
+
def _wins_over(
|
|
145
|
+
candidate: FeatureContext[FeatureMetadataT],
|
|
146
|
+
incumbent: typing.Optional[FeatureContext[FeatureMetadataT]],
|
|
147
|
+
) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Whether a segment override takes precedence over the one held so far.
|
|
150
|
+
|
|
151
|
+
Lower priority wins, and the first seen wins a tie, so that precedence
|
|
152
|
+
follows context order rather than the order segments happen to be
|
|
153
|
+
evaluated in.
|
|
154
|
+
"""
|
|
155
|
+
return incumbent is None or candidate.get(
|
|
156
|
+
"priority", constants.DEFAULT_PRIORITY
|
|
157
|
+
) < incumbent.get("priority", constants.DEFAULT_PRIORITY)
|
|
158
|
+
|
|
159
|
+
|
|
93
160
|
def evaluate_segments(
|
|
94
161
|
context: EvaluationContext[SegmentMetadataT, FeatureMetadataT],
|
|
162
|
+
flags: "_LazyFlags",
|
|
95
163
|
) -> typing.Tuple[
|
|
96
164
|
list[SegmentResult[SegmentMetadataT]],
|
|
97
165
|
SegmentOverrides[FeatureMetadataT],
|
|
@@ -101,9 +169,18 @@ def evaluate_segments(
|
|
|
101
169
|
|
|
102
170
|
segment_results: list[SegmentResult[SegmentMetadataT]] = []
|
|
103
171
|
segment_overrides: SegmentOverrides[FeatureMetadataT] = {}
|
|
172
|
+
cycle_hits = flags.cycle_hits
|
|
104
173
|
|
|
105
174
|
for segment_context in segment_contexts.values():
|
|
106
|
-
|
|
175
|
+
matches = is_context_in_segment(context, segment_context)
|
|
176
|
+
|
|
177
|
+
if (hits := flags.cycle_hits) != cycle_hits:
|
|
178
|
+
# Evaluating this segment cut a cycle, so it matched, or failed to,
|
|
179
|
+
# on a flag that could not be resolved. Neither verdict is sound.
|
|
180
|
+
cycle_hits = hits
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
if not matches:
|
|
107
184
|
continue
|
|
108
185
|
|
|
109
186
|
segment_result: SegmentResult[SegmentMetadataT] = {
|
|
@@ -113,24 +190,17 @@ def evaluate_segments(
|
|
|
113
190
|
segment_result["metadata"] = segment_metadata
|
|
114
191
|
segment_results.append(segment_result)
|
|
115
192
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
constants.DEFAULT_PRIORITY,
|
|
128
|
-
)
|
|
129
|
-
):
|
|
130
|
-
segment_overrides[feature_name] = SegmentOverride(
|
|
131
|
-
feature_context=override_feature_context,
|
|
132
|
-
segment_name=segment_context["name"],
|
|
133
|
-
)
|
|
193
|
+
for override_feature_context in segment_context.get("overrides") or ():
|
|
194
|
+
feature_name = override_feature_context["name"]
|
|
195
|
+
incumbent = segment_overrides.get(feature_name)
|
|
196
|
+
if _wins_over(
|
|
197
|
+
override_feature_context,
|
|
198
|
+
incumbent["feature_context"] if incumbent else None,
|
|
199
|
+
):
|
|
200
|
+
segment_overrides[feature_name] = SegmentOverride(
|
|
201
|
+
feature_context=override_feature_context,
|
|
202
|
+
segment_name=segment_context["name"],
|
|
203
|
+
)
|
|
134
204
|
|
|
135
205
|
return segment_results, segment_overrides
|
|
136
206
|
|
|
@@ -162,6 +232,115 @@ def evaluate_features(
|
|
|
162
232
|
return flags
|
|
163
233
|
|
|
164
234
|
|
|
235
|
+
_JSONPATH_PREFIX = "$."
|
|
236
|
+
|
|
237
|
+
CIRCULAR_DEPENDENCY_REASON = "ERROR; code=CIRCULAR_DEPENDENCY"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class _DependencyResolver(typing.Generic[SegmentMetadataT, FeatureMetadataT]):
|
|
241
|
+
"""
|
|
242
|
+
Resolves overrides for a context with flag dependencies, memoising the results.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
def __init__(
|
|
246
|
+
self,
|
|
247
|
+
context: EvaluationContext[SegmentMetadataT, FeatureMetadataT],
|
|
248
|
+
flags: "_LazyFlags",
|
|
249
|
+
) -> None:
|
|
250
|
+
self._context = context
|
|
251
|
+
self._flags = flags
|
|
252
|
+
self._segment_matches: dict[str, bool] = {}
|
|
253
|
+
self._resolving: list[str] = []
|
|
254
|
+
self._segment_keys_by_feature_name: dict[str, list[str]] = {}
|
|
255
|
+
for segment_key, segment_context in (context.get("segments") or {}).items():
|
|
256
|
+
for override in segment_context.get("overrides") or ():
|
|
257
|
+
self._segment_keys_by_feature_name.setdefault(
|
|
258
|
+
override["name"], []
|
|
259
|
+
).append(segment_key)
|
|
260
|
+
|
|
261
|
+
def resolve_feature(self, feature_name: str) -> None:
|
|
262
|
+
if feature_name in self._resolving:
|
|
263
|
+
# Cyclic dependency. Leave the flag unresolved.
|
|
264
|
+
cycle_start = self._resolving.index(feature_name)
|
|
265
|
+
self._flags.cyclic.update(self._resolving[cycle_start:])
|
|
266
|
+
self._flags.cycle_hits += 1
|
|
267
|
+
return
|
|
268
|
+
if not (
|
|
269
|
+
feature_context := (self._context.get("features") or {}).get(feature_name)
|
|
270
|
+
):
|
|
271
|
+
# Depending on a feature absent from the context.
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
self._resolving.append(feature_name)
|
|
275
|
+
try:
|
|
276
|
+
segment_override = self._get_segment_override(feature_name)
|
|
277
|
+
finally:
|
|
278
|
+
# A `KeyError` raised under here is swallowed by the JSONPath
|
|
279
|
+
# implementation and evaluation carries on, so a name left on the
|
|
280
|
+
# stack would silently look like a cycle to a later read.
|
|
281
|
+
self._resolving.pop()
|
|
282
|
+
|
|
283
|
+
if feature_name in self._flags.cyclic:
|
|
284
|
+
# The result is not something another condition may match on.
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
if segment_override is not None:
|
|
288
|
+
segment_name = segment_override["segment_name"]
|
|
289
|
+
self._flags[feature_name] = get_flag_result_from_context(
|
|
290
|
+
context=self._context,
|
|
291
|
+
feature_context=segment_override["feature_context"],
|
|
292
|
+
reason=f"TARGETING_MATCH; segment={segment_name}",
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
self._flags[feature_name] = get_flag_result_from_context(
|
|
296
|
+
context=self._context,
|
|
297
|
+
feature_context=feature_context,
|
|
298
|
+
reason="DEFAULT",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def matches_segment(self, segment_key: str) -> bool:
|
|
302
|
+
if (matches := self._segment_matches.get(segment_key)) is not None:
|
|
303
|
+
return matches
|
|
304
|
+
|
|
305
|
+
cycle_hits = self._flags.cycle_hits
|
|
306
|
+
matches = is_context_in_segment(
|
|
307
|
+
self._context,
|
|
308
|
+
(self._context.get("segments") or {})[segment_key],
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if self._flags.cycle_hits != cycle_hits:
|
|
312
|
+
# Reached by cutting a cycle, so the verdict rests on a flag that
|
|
313
|
+
# could not be resolved. Not a match, and not worth memoising.
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
self._segment_matches[segment_key] = matches
|
|
317
|
+
return matches
|
|
318
|
+
|
|
319
|
+
def _get_segment_override(
|
|
320
|
+
self,
|
|
321
|
+
feature_name: str,
|
|
322
|
+
) -> typing.Optional[SegmentOverride[FeatureMetadataT]]:
|
|
323
|
+
segment_override: typing.Optional[SegmentOverride[FeatureMetadataT]] = None
|
|
324
|
+
|
|
325
|
+
for segment_key in self._segment_keys_by_feature_name.get(feature_name) or ():
|
|
326
|
+
if not self.matches_segment(segment_key):
|
|
327
|
+
continue
|
|
328
|
+
segment_context = (self._context.get("segments") or {})[segment_key]
|
|
329
|
+
for override_feature_context in segment_context.get("overrides") or ():
|
|
330
|
+
if override_feature_context["name"] != feature_name:
|
|
331
|
+
continue
|
|
332
|
+
if _wins_over(
|
|
333
|
+
override_feature_context,
|
|
334
|
+
segment_override["feature_context"] if segment_override else None,
|
|
335
|
+
):
|
|
336
|
+
segment_override = SegmentOverride(
|
|
337
|
+
feature_context=override_feature_context,
|
|
338
|
+
segment_name=segment_context["name"],
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
return segment_override
|
|
342
|
+
|
|
343
|
+
|
|
165
344
|
def get_flag_result_from_context(
|
|
166
345
|
context: _EvaluationContextAnyMeta,
|
|
167
346
|
feature_context: FeatureContext[FeatureMetadataT],
|
|
@@ -321,7 +500,7 @@ def get_context_value(
|
|
|
321
500
|
property: str,
|
|
322
501
|
) -> ContextValue:
|
|
323
502
|
value = None
|
|
324
|
-
if property.startswith(
|
|
503
|
+
if property.startswith(_JSONPATH_PREFIX):
|
|
325
504
|
value = _get_context_value_getter(property)(context)
|
|
326
505
|
else:
|
|
327
506
|
value = _get_trait_value(context, property)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flagsmith-flag-engine
|
|
3
|
-
Version:
|
|
3
|
+
Version: 11.1.0
|
|
4
4
|
Summary: Flag engine for the Flagsmith API.
|
|
5
5
|
Home-page: https://github.com/Flagsmith/flagsmith-engine
|
|
6
6
|
Author: Flagsmith
|
|
@@ -8,11 +8,12 @@ Author-email: support@flagsmith.com
|
|
|
8
8
|
License: BSD3
|
|
9
9
|
Classifier: License :: OSI Approved :: BSD License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.10
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
18
|
License-File: LICENSE.txt
|
|
18
19
|
Requires-Dist: jsonpath-rfc9535<1,>=0.1.5
|
|
@@ -27,6 +28,7 @@ Dynamic: home-page
|
|
|
27
28
|
Dynamic: license
|
|
28
29
|
Dynamic: license-file
|
|
29
30
|
Dynamic: requires-dist
|
|
31
|
+
Dynamic: requires-python
|
|
30
32
|
Dynamic: summary
|
|
31
33
|
|
|
32
34
|
[](https://www.flagsmith.com/)
|
|
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="flagsmith-flag-engine",
|
|
5
|
-
version="
|
|
5
|
+
version="11.1.0",
|
|
6
6
|
author="Flagsmith",
|
|
7
7
|
author_email="support@flagsmith.com",
|
|
8
8
|
packages=find_packages(include=["flag_engine", "flag_engine.*"]),
|
|
@@ -12,6 +12,7 @@ setup(
|
|
|
12
12
|
description="Flag engine for the Flagsmith API.",
|
|
13
13
|
long_description=open("README.md").read(),
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
15
|
+
python_requires=">=3.10",
|
|
15
16
|
install_requires=[
|
|
16
17
|
"jsonpath-rfc9535>=0.1.5,<1",
|
|
17
18
|
"semver>=3.0.4,<4",
|
|
@@ -20,10 +21,10 @@ setup(
|
|
|
20
21
|
classifiers=[
|
|
21
22
|
"License :: OSI Approved :: BSD License",
|
|
22
23
|
"Programming Language :: Python :: 3",
|
|
23
|
-
"Programming Language :: Python :: 3.9",
|
|
24
24
|
"Programming Language :: Python :: 3.10",
|
|
25
25
|
"Programming Language :: Python :: 3.11",
|
|
26
26
|
"Programming Language :: Python :: 3.12",
|
|
27
27
|
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
28
29
|
],
|
|
29
30
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/context/__init__.py
RENAMED
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/context/mappers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/result/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/__init__.py
RENAMED
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/segments/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/json/__init__.py
RENAMED
|
File without changes
|
{flagsmith_flag_engine-10.2.0 → flagsmith_flag_engine-11.1.0}/flag_engine/utils/json/encoders.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|