flagsmith 3.6.0__tar.gz → 3.8.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-3.6.0 → flagsmith-3.8.0}/PKG-INFO +7 -7
- flagsmith-3.6.0/Readme.md → flagsmith-3.8.0/README.md +3 -3
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/exceptions.py +4 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/flagsmith.py +58 -69
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/models.py +4 -2
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/polling_manager.py +1 -8
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/streaming_manager.py +9 -15
- flagsmith-3.8.0/flagsmith/types.py +25 -0
- flagsmith-3.8.0/flagsmith/utils/identities.py +26 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/pyproject.toml +9 -12
- flagsmith-3.6.0/flagsmith/utils/identities.py +0 -19
- {flagsmith-3.6.0 → flagsmith-3.8.0}/LICENSE +0 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/__init__.py +0 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/analytics.py +0 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/offline_handlers.py +0 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/py.typed +0 -0
- {flagsmith-3.6.0 → flagsmith-3.8.0}/flagsmith/utils/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flagsmith
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: Flagsmith Python SDK
|
|
5
5
|
License: BSD3
|
|
6
6
|
Keywords: feature,flag,flagsmith,remote,config
|
|
@@ -14,9 +14,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Requires-Dist: flagsmith-flag-engine (>=5.1.0,<6.0.0)
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist: requests (>=2.
|
|
19
|
-
Requires-Dist: requests-futures (>=1.0.
|
|
17
|
+
Requires-Dist: pydantic (>=2,<3)
|
|
18
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
19
|
+
Requires-Dist: requests-futures (>=1.0.1,<2.0.0)
|
|
20
20
|
Requires-Dist: sseclient-py (>=1.8.0,<2.0.0)
|
|
21
21
|
Project-URL: Documentation, https://docs.flagsmith.com
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
@@ -33,12 +33,12 @@ The SDK for Python applications for [https://www.flagsmith.com/](https://www.fla
|
|
|
33
33
|
## Adding to your project
|
|
34
34
|
|
|
35
35
|
For full documentation visit
|
|
36
|
-
[https://docs.flagsmith.com/clients/server-side](https://docs.flagsmith.com/clients/server-side).
|
|
36
|
+
[https://docs.flagsmith.com/clients/server-side?language=python](https://docs.flagsmith.com/clients/server-side?language=python).
|
|
37
37
|
|
|
38
38
|
## Contributing
|
|
39
39
|
|
|
40
|
-
Please read [CONTRIBUTING.md](
|
|
41
|
-
|
|
40
|
+
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull
|
|
41
|
+
requests
|
|
42
42
|
|
|
43
43
|
## Getting Help
|
|
44
44
|
|
|
@@ -10,12 +10,12 @@ The SDK for Python applications for [https://www.flagsmith.com/](https://www.fla
|
|
|
10
10
|
## Adding to your project
|
|
11
11
|
|
|
12
12
|
For full documentation visit
|
|
13
|
-
[https://docs.flagsmith.com/clients/server-side](https://docs.flagsmith.com/clients/server-side).
|
|
13
|
+
[https://docs.flagsmith.com/clients/server-side?language=python](https://docs.flagsmith.com/clients/server-side?language=python).
|
|
14
14
|
|
|
15
15
|
## Contributing
|
|
16
16
|
|
|
17
|
-
Please read [CONTRIBUTING.md](
|
|
18
|
-
|
|
17
|
+
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull
|
|
18
|
+
requests
|
|
19
19
|
|
|
20
20
|
## Getting Help
|
|
21
21
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import typing
|
|
4
|
-
from datetime import
|
|
3
|
+
from datetime import timezone
|
|
5
4
|
|
|
6
|
-
import
|
|
5
|
+
import pydantic
|
|
7
6
|
import requests
|
|
8
7
|
from flag_engine import engine
|
|
9
8
|
from flag_engine.environments.models import EnvironmentModel
|
|
@@ -20,23 +19,14 @@ from flagsmith.models import DefaultFlag, Flags, Segment
|
|
|
20
19
|
from flagsmith.offline_handlers import BaseOfflineHandler
|
|
21
20
|
from flagsmith.polling_manager import EnvironmentDataPollingManager
|
|
22
21
|
from flagsmith.streaming_manager import EventStreamManager, StreamEvent
|
|
23
|
-
from flagsmith.
|
|
22
|
+
from flagsmith.types import JsonType, TraitConfig, TraitMapping
|
|
23
|
+
from flagsmith.utils.identities import generate_identity_data
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
26
|
|
|
27
27
|
DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/"
|
|
28
28
|
DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/"
|
|
29
29
|
|
|
30
|
-
JsonType = typing.Union[
|
|
31
|
-
None,
|
|
32
|
-
int,
|
|
33
|
-
str,
|
|
34
|
-
bool,
|
|
35
|
-
typing.List["JsonType"],
|
|
36
|
-
typing.List[typing.Mapping[str, "JsonType"]],
|
|
37
|
-
typing.Dict[str, "JsonType"],
|
|
38
|
-
]
|
|
39
|
-
|
|
40
30
|
|
|
41
31
|
class Flagsmith:
|
|
42
32
|
"""A Flagsmith client.
|
|
@@ -169,8 +159,10 @@ class Flagsmith:
|
|
|
169
159
|
)
|
|
170
160
|
|
|
171
161
|
def _initialise_local_evaluation(self) -> None:
|
|
162
|
+
# To ensure that the environment is set before allowing subsequent
|
|
163
|
+
# method calls, update the environment manually.
|
|
164
|
+
self.update_environment()
|
|
172
165
|
if self.enable_realtime_updates:
|
|
173
|
-
self.update_environment()
|
|
174
166
|
if not self._environment:
|
|
175
167
|
raise ValueError("Unable to get environment from API key")
|
|
176
168
|
|
|
@@ -185,9 +177,6 @@ class Flagsmith:
|
|
|
185
177
|
self.event_stream_thread.start()
|
|
186
178
|
|
|
187
179
|
else:
|
|
188
|
-
# To ensure that the environment is set before allowing subsequent
|
|
189
|
-
# method calls, update the environment manually.
|
|
190
|
-
self.update_environment()
|
|
191
180
|
self.environment_data_polling_manager_thread = (
|
|
192
181
|
EnvironmentDataPollingManager(
|
|
193
182
|
main=self,
|
|
@@ -199,30 +188,15 @@ class Flagsmith:
|
|
|
199
188
|
self.environment_data_polling_manager_thread.start()
|
|
200
189
|
|
|
201
190
|
def handle_stream_event(self, event: StreamEvent) -> None:
|
|
202
|
-
try:
|
|
203
|
-
event_data = json.loads(event.data)
|
|
204
|
-
except json.JSONDecodeError as e:
|
|
205
|
-
raise FlagsmithAPIError("Unable to get valid json from event data.") from e
|
|
206
|
-
|
|
207
|
-
try:
|
|
208
|
-
stream_updated_at = datetime.fromtimestamp(event_data.get("updated_at"))
|
|
209
|
-
except TypeError as e:
|
|
210
|
-
raise FlagsmithAPIError(
|
|
211
|
-
"Unable to get valid timestamp from event data."
|
|
212
|
-
) from e
|
|
213
|
-
|
|
214
|
-
if stream_updated_at.tzinfo is None:
|
|
215
|
-
stream_updated_at = pytz.utc.localize(stream_updated_at)
|
|
216
|
-
|
|
217
191
|
if not self._environment:
|
|
218
192
|
raise ValueError(
|
|
219
193
|
"Unable to access environment. Environment should not be null"
|
|
220
194
|
)
|
|
221
195
|
environment_updated_at = self._environment.updated_at
|
|
222
196
|
if environment_updated_at.tzinfo is None:
|
|
223
|
-
environment_updated_at =
|
|
197
|
+
environment_updated_at = environment_updated_at.astimezone(timezone.utc)
|
|
224
198
|
|
|
225
|
-
if
|
|
199
|
+
if event.updated_at > environment_updated_at:
|
|
226
200
|
self.update_environment()
|
|
227
201
|
|
|
228
202
|
def get_environment_flags(self) -> Flags:
|
|
@@ -238,7 +212,9 @@ class Flagsmith:
|
|
|
238
212
|
def get_identity_flags(
|
|
239
213
|
self,
|
|
240
214
|
identifier: str,
|
|
241
|
-
traits: typing.Optional[
|
|
215
|
+
traits: typing.Optional[TraitMapping] = None,
|
|
216
|
+
*,
|
|
217
|
+
transient: bool = False,
|
|
242
218
|
) -> Flags:
|
|
243
219
|
"""
|
|
244
220
|
Get all the flags for the current environment for a given identity. Will also
|
|
@@ -248,13 +224,20 @@ class Flagsmith:
|
|
|
248
224
|
:param identifier: a unique identifier for the identity in the current
|
|
249
225
|
environment, e.g. email address, username, uuid
|
|
250
226
|
:param traits: a dictionary of traits to add / update on the identity in
|
|
251
|
-
Flagsmith, e.g. {"num_orders": 10}
|
|
227
|
+
Flagsmith, e.g. `{"num_orders": 10}`. Envelope traits you don't want persisted
|
|
228
|
+
in a dictionary with `"transient"` and `"value"` keys, e.g.
|
|
229
|
+
`{"num_orders": 10, "color": {"value": "pink", "transient": True}}`.
|
|
230
|
+
:param transient: if `True`, the identity won't get persisted
|
|
252
231
|
:return: Flags object holding all the flags for the given identity.
|
|
253
232
|
"""
|
|
254
233
|
traits = traits or {}
|
|
255
234
|
if (self.offline_mode or self.enable_local_evaluation) and self._environment:
|
|
256
235
|
return self._get_identity_flags_from_document(identifier, traits)
|
|
257
|
-
return self._get_identity_flags_from_api(
|
|
236
|
+
return self._get_identity_flags_from_api(
|
|
237
|
+
identifier,
|
|
238
|
+
traits,
|
|
239
|
+
transient=transient,
|
|
240
|
+
)
|
|
258
241
|
|
|
259
242
|
def get_identity_segments(
|
|
260
243
|
self,
|
|
@@ -282,16 +265,15 @@ class Flagsmith:
|
|
|
282
265
|
return [Segment(id=sm.id, name=sm.name) for sm in segment_models]
|
|
283
266
|
|
|
284
267
|
def update_environment(self) -> None:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
268
|
+
try:
|
|
269
|
+
self._environment = self._get_environment_from_api()
|
|
270
|
+
except (FlagsmithAPIError, pydantic.ValidationError):
|
|
271
|
+
logger.exception("Error updating environment")
|
|
272
|
+
else:
|
|
273
|
+
if overrides := self._environment.identity_overrides:
|
|
274
|
+
self._identity_overrides_by_identifier = {
|
|
275
|
+
identity.identifier: identity for identity in overrides
|
|
276
|
+
}
|
|
295
277
|
|
|
296
278
|
def _get_environment_from_api(self) -> EnvironmentModel:
|
|
297
279
|
environment_data = self._get_json_response(self.environment_url, method="GET")
|
|
@@ -307,7 +289,7 @@ class Flagsmith:
|
|
|
307
289
|
)
|
|
308
290
|
|
|
309
291
|
def _get_identity_flags_from_document(
|
|
310
|
-
self, identifier: str, traits:
|
|
292
|
+
self, identifier: str, traits: TraitMapping
|
|
311
293
|
) -> Flags:
|
|
312
294
|
identity_model = self._get_identity_model(identifier, **traits)
|
|
313
295
|
if self._environment is None:
|
|
@@ -324,9 +306,9 @@ class Flagsmith:
|
|
|
324
306
|
|
|
325
307
|
def _get_environment_flags_from_api(self) -> Flags:
|
|
326
308
|
try:
|
|
327
|
-
json_response: typing.List[
|
|
328
|
-
|
|
329
|
-
|
|
309
|
+
json_response: typing.List[typing.Mapping[str, JsonType]] = (
|
|
310
|
+
self._get_json_response(url=self.environment_flags_url, method="GET")
|
|
311
|
+
)
|
|
330
312
|
return Flags.from_api_flags(
|
|
331
313
|
api_flags=json_response,
|
|
332
314
|
analytics_processor=self._analytics_processor,
|
|
@@ -340,14 +322,24 @@ class Flagsmith:
|
|
|
340
322
|
raise
|
|
341
323
|
|
|
342
324
|
def _get_identity_flags_from_api(
|
|
343
|
-
self,
|
|
325
|
+
self,
|
|
326
|
+
identifier: str,
|
|
327
|
+
traits: TraitMapping,
|
|
328
|
+
*,
|
|
329
|
+
transient: bool = False,
|
|
344
330
|
) -> Flags:
|
|
331
|
+
request_body = generate_identity_data(
|
|
332
|
+
identifier,
|
|
333
|
+
traits,
|
|
334
|
+
transient=transient,
|
|
335
|
+
)
|
|
345
336
|
try:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
337
|
+
json_response: typing.Dict[str, typing.List[typing.Dict[str, JsonType]]] = (
|
|
338
|
+
self._get_json_response(
|
|
339
|
+
url=self.identities_url,
|
|
340
|
+
method="POST",
|
|
341
|
+
body=request_body,
|
|
342
|
+
)
|
|
351
343
|
)
|
|
352
344
|
return Flags.from_api_flags(
|
|
353
345
|
api_flags=json_response["flags"],
|
|
@@ -365,22 +357,16 @@ class Flagsmith:
|
|
|
365
357
|
self,
|
|
366
358
|
url: str,
|
|
367
359
|
method: str,
|
|
368
|
-
body: typing.Optional[
|
|
369
|
-
typing.Union[Identity, typing.Dict[str, JsonType]]
|
|
370
|
-
] = None,
|
|
360
|
+
body: typing.Optional[JsonType] = None,
|
|
371
361
|
) -> typing.Any:
|
|
372
362
|
try:
|
|
373
363
|
request_method = getattr(self.session, method.lower())
|
|
374
364
|
response = request_method(
|
|
375
365
|
url, json=body, timeout=self.request_timeout_seconds
|
|
376
366
|
)
|
|
377
|
-
|
|
378
|
-
raise FlagsmithAPIError(
|
|
379
|
-
"Invalid request made to Flagsmith API. Response status code: %d",
|
|
380
|
-
response.status_code,
|
|
381
|
-
)
|
|
367
|
+
response.raise_for_status()
|
|
382
368
|
return response.json()
|
|
383
|
-
except
|
|
369
|
+
except requests.RequestException as e:
|
|
384
370
|
raise FlagsmithAPIError(
|
|
385
371
|
"Unable to get valid response from Flagsmith API."
|
|
386
372
|
) from e
|
|
@@ -388,7 +374,7 @@ class Flagsmith:
|
|
|
388
374
|
def _get_identity_model(
|
|
389
375
|
self,
|
|
390
376
|
identifier: str,
|
|
391
|
-
**traits: TraitValue,
|
|
377
|
+
**traits: typing.Union[TraitValue, TraitConfig],
|
|
392
378
|
) -> IdentityModel:
|
|
393
379
|
if not self._environment:
|
|
394
380
|
raise FlagsmithClientError(
|
|
@@ -396,7 +382,10 @@ class Flagsmith:
|
|
|
396
382
|
)
|
|
397
383
|
|
|
398
384
|
trait_models = [
|
|
399
|
-
TraitModel(
|
|
385
|
+
TraitModel(
|
|
386
|
+
trait_key=key,
|
|
387
|
+
trait_value=value["value"] if isinstance(value, dict) else value,
|
|
388
|
+
)
|
|
400
389
|
for key, value in traits.items()
|
|
401
390
|
]
|
|
402
391
|
|
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
|
|
|
6
6
|
from flag_engine.features.models import FeatureStateModel
|
|
7
7
|
|
|
8
8
|
from flagsmith.analytics import AnalyticsProcessor
|
|
9
|
-
from flagsmith.exceptions import
|
|
9
|
+
from flagsmith.exceptions import FlagsmithFeatureDoesNotExistError
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass
|
|
@@ -135,7 +135,9 @@ class Flags:
|
|
|
135
135
|
except KeyError:
|
|
136
136
|
if self.default_flag_handler:
|
|
137
137
|
return self.default_flag_handler(feature_name)
|
|
138
|
-
raise
|
|
138
|
+
raise FlagsmithFeatureDoesNotExistError(
|
|
139
|
+
"Feature does not exist: %s" % feature_name
|
|
140
|
+
)
|
|
139
141
|
|
|
140
142
|
if self._analytics_processor and hasattr(flag, "feature_name"):
|
|
141
143
|
self._analytics_processor.track_feature(flag.feature_name)
|
|
@@ -5,10 +5,6 @@ import threading
|
|
|
5
5
|
import time
|
|
6
6
|
import typing
|
|
7
7
|
|
|
8
|
-
import requests
|
|
9
|
-
|
|
10
|
-
from flagsmith.exceptions import FlagsmithAPIError
|
|
11
|
-
|
|
12
8
|
if typing.TYPE_CHECKING:
|
|
13
9
|
from flagsmith import Flagsmith
|
|
14
10
|
|
|
@@ -30,10 +26,7 @@ class EnvironmentDataPollingManager(threading.Thread):
|
|
|
30
26
|
|
|
31
27
|
def run(self) -> None:
|
|
32
28
|
while not self._stop_event.is_set():
|
|
33
|
-
|
|
34
|
-
self.main.update_environment()
|
|
35
|
-
except (FlagsmithAPIError, requests.RequestException):
|
|
36
|
-
logger.exception("Failed to update environment")
|
|
29
|
+
self.main.update_environment()
|
|
37
30
|
time.sleep(self.refresh_interval_seconds)
|
|
38
31
|
|
|
39
32
|
def stop(self) -> None:
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import threading
|
|
3
3
|
import typing
|
|
4
|
-
from typing import Callable,
|
|
4
|
+
from typing import Callable, Optional
|
|
5
5
|
|
|
6
|
+
import pydantic
|
|
6
7
|
import requests
|
|
7
8
|
import sseclient
|
|
8
9
|
|
|
9
|
-
from flagsmith.exceptions import FlagsmithAPIError
|
|
10
|
-
|
|
11
10
|
logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
class StreamEvent(
|
|
15
|
-
|
|
13
|
+
class StreamEvent(pydantic.BaseModel):
|
|
14
|
+
updated_at: pydantic.AwareDatetime
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class EventStreamManager(threading.Thread):
|
|
@@ -22,7 +21,7 @@ class EventStreamManager(threading.Thread):
|
|
|
22
21
|
stream_url: str,
|
|
23
22
|
on_event: Callable[[StreamEvent], None],
|
|
24
23
|
request_timeout_seconds: Optional[int] = None,
|
|
25
|
-
**kwargs: typing.Any
|
|
24
|
+
**kwargs: typing.Any,
|
|
26
25
|
) -> None:
|
|
27
26
|
super().__init__(*args, **kwargs)
|
|
28
27
|
self._stop_event = threading.Event()
|
|
@@ -39,17 +38,12 @@ class EventStreamManager(threading.Thread):
|
|
|
39
38
|
headers={"Accept": "application/json, text/event-stream"},
|
|
40
39
|
timeout=self.request_timeout_seconds,
|
|
41
40
|
) as response:
|
|
42
|
-
sse_client = sseclient.SSEClient(
|
|
43
|
-
cast(Generator[bytes, None, None], response)
|
|
44
|
-
)
|
|
41
|
+
sse_client = sseclient.SSEClient(chunk for chunk in response)
|
|
45
42
|
for event in sse_client.events():
|
|
46
|
-
self.on_event(event)
|
|
47
|
-
|
|
48
|
-
except requests.exceptions.ReadTimeout:
|
|
49
|
-
pass
|
|
43
|
+
self.on_event(StreamEvent.model_validate_json(event.data))
|
|
50
44
|
|
|
51
|
-
except (
|
|
52
|
-
logger.exception("Error
|
|
45
|
+
except (requests.RequestException, pydantic.ValidationError):
|
|
46
|
+
logger.exception("Error opening or reading from the event stream")
|
|
53
47
|
|
|
54
48
|
def stop(self) -> None:
|
|
55
49
|
self._stop_event.set()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from flag_engine.identities.traits.types import TraitValue
|
|
4
|
+
from typing_extensions import TypeAlias
|
|
5
|
+
|
|
6
|
+
_JsonScalarType: TypeAlias = typing.Union[
|
|
7
|
+
int,
|
|
8
|
+
str,
|
|
9
|
+
float,
|
|
10
|
+
bool,
|
|
11
|
+
None,
|
|
12
|
+
]
|
|
13
|
+
JsonType: TypeAlias = typing.Union[
|
|
14
|
+
_JsonScalarType,
|
|
15
|
+
typing.Dict[str, "JsonType"],
|
|
16
|
+
typing.List["JsonType"],
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TraitConfig(typing.TypedDict):
|
|
21
|
+
value: TraitValue
|
|
22
|
+
transient: bool
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
TraitMapping: TypeAlias = typing.Mapping[str, typing.Union[TraitValue, TraitConfig]]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from flagsmith.types import JsonType, TraitMapping
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_identity_data(
|
|
7
|
+
identifier: str,
|
|
8
|
+
traits: TraitMapping,
|
|
9
|
+
*,
|
|
10
|
+
transient: bool,
|
|
11
|
+
) -> JsonType:
|
|
12
|
+
identity_data: typing.Dict[str, JsonType] = {"identifier": identifier}
|
|
13
|
+
traits_data: typing.List[JsonType] = []
|
|
14
|
+
for trait_key, trait_value in traits.items():
|
|
15
|
+
trait_data: typing.Dict[str, JsonType] = {"trait_key": trait_key}
|
|
16
|
+
if isinstance(trait_value, dict):
|
|
17
|
+
trait_data["trait_value"] = trait_value["value"]
|
|
18
|
+
if trait_value.get("transient"):
|
|
19
|
+
trait_data["transient"] = True
|
|
20
|
+
else:
|
|
21
|
+
trait_data["trait_value"] = trait_value
|
|
22
|
+
traits_data.append(trait_data)
|
|
23
|
+
identity_data["traits"] = traits_data
|
|
24
|
+
if transient:
|
|
25
|
+
identity_data["transient"] = True
|
|
26
|
+
return identity_data
|
|
@@ -1,42 +1,39 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "flagsmith"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.8.0"
|
|
4
4
|
description = "Flagsmith Python SDK"
|
|
5
5
|
authors = ["Flagsmith <support@flagsmith.com>"]
|
|
6
6
|
license = "BSD3"
|
|
7
|
-
readme = "
|
|
7
|
+
readme = "README.md"
|
|
8
8
|
keywords = ["feature", "flag", "flagsmith", "remote", "config"]
|
|
9
9
|
documentation = "https://docs.flagsmith.com"
|
|
10
10
|
packages = [{ include = "flagsmith" }]
|
|
11
11
|
|
|
12
12
|
[tool.poetry.dependencies]
|
|
13
13
|
python = ">=3.8.1,<4"
|
|
14
|
-
requests = "^2.
|
|
15
|
-
requests-futures = "^1.0.
|
|
14
|
+
requests = "^2.32.3"
|
|
15
|
+
requests-futures = "^1.0.1"
|
|
16
16
|
flagsmith-flag-engine = "^5.1.0"
|
|
17
17
|
sseclient-py = "^1.8.0"
|
|
18
|
-
|
|
18
|
+
pydantic = "^2"
|
|
19
19
|
|
|
20
20
|
[tool.poetry.group.dev]
|
|
21
21
|
optional = true
|
|
22
22
|
|
|
23
23
|
[tool.poetry.group.dev.dependencies]
|
|
24
24
|
pytest = "^7.4.0"
|
|
25
|
+
pytest-cov = "^4.1.0"
|
|
25
26
|
pytest-mock = "^3.6.1"
|
|
26
|
-
black = "^23.3.0"
|
|
27
27
|
pre-commit = "^2.17.0"
|
|
28
28
|
responses = "^0.24.1"
|
|
29
|
-
|
|
30
|
-
isort = "^5.12.0"
|
|
31
|
-
mypy = "^1.7.1"
|
|
32
|
-
types-requests = "^2.31.0.10"
|
|
33
|
-
pytest-cov = "^4.1.0"
|
|
34
|
-
types-pytz = "^2024.1.0.20240203"
|
|
29
|
+
types-requests = "^2.32"
|
|
35
30
|
|
|
36
31
|
[tool.mypy]
|
|
37
32
|
plugins = ["pydantic.mypy"]
|
|
38
33
|
exclude = ["example/*"]
|
|
39
34
|
|
|
35
|
+
[tool.black]
|
|
36
|
+
target-version = ["py38"]
|
|
40
37
|
|
|
41
38
|
[build-system]
|
|
42
39
|
requires = ["poetry-core>=1.0.0"]
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from flag_engine.identities.traits.types import TraitValue
|
|
4
|
-
|
|
5
|
-
Identity = typing.TypedDict(
|
|
6
|
-
"Identity",
|
|
7
|
-
{"identifier": str, "traits": typing.List[typing.Mapping[str, TraitValue]]},
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def generate_identities_data(
|
|
12
|
-
identifier: str, traits: typing.Optional[typing.Mapping[str, TraitValue]] = None
|
|
13
|
-
) -> Identity:
|
|
14
|
-
return {
|
|
15
|
-
"identifier": identifier,
|
|
16
|
-
"traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()]
|
|
17
|
-
if traits
|
|
18
|
-
else [],
|
|
19
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|