Skip to content

Commit 3eaddf6

Browse files
committed
ditch pydantic dependency
1 parent 38b1e1b commit 3eaddf6

File tree

8 files changed

+37
-173
lines changed

8 files changed

+37
-173
lines changed

flagsmith/api/__init__.py

Whitespace-only changes.

flagsmith/flagsmith.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def handle_stream_event(self, event: StreamEvent) -> None:
207207
raise ValueError(
208208
"Cannot handle stream events before retrieving initial environment"
209209
)
210-
if event.updated_at > environment_updated_at:
210+
if event["updated_at"] > environment_updated_at:
211211
self.update_environment()
212212

213213
def get_environment_flags(self) -> Flags:

flagsmith/mappers.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
import json
12
import typing
23
from collections import defaultdict
34
from datetime import datetime, timezone
45
from operator import itemgetter
56

7+
import sseclient
68
from flag_engine.context.types import (
79
EvaluationContext,
810
FeatureContext,
911
SegmentContext,
1012
SegmentRule,
1113
)
1214

15+
from flagsmith.types import StreamEvent
16+
1317
OverrideKey = typing.Tuple[
1418
str,
1519
str,
@@ -19,13 +23,24 @@
1923
OverridesKey = typing.Tuple[OverrideKey, ...]
2024

2125

26+
def map_sse_event_to_stream_event(event: sseclient.Event) -> StreamEvent:
27+
event_data = json.loads(event.data)
28+
return {
29+
"updated_at": datetime.fromtimestamp(
30+
event_data["updated_at"],
31+
tz=timezone.utc,
32+
)
33+
}
34+
35+
2236
def map_environment_document_to_environment_updated_at(
2337
environment_document: dict[str, typing.Any],
2438
) -> datetime:
25-
updated_at = datetime.fromisoformat(environment_document["updated_at"])
26-
if updated_at.tzinfo is None:
27-
return updated_at.astimezone(tz=timezone.utc)
28-
return updated_at
39+
if (
40+
updated_at := datetime.fromisoformat(environment_document["updated_at"])
41+
).tzinfo is None:
42+
return updated_at.replace(tzinfo=timezone.utc)
43+
return updated_at.astimezone(tz=timezone.utc)
2944

3045

3146
def map_environment_document_to_context(

flagsmith/streaming_manager.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
import typing
44
from typing import Callable, Optional
55

6-
import pydantic
76
import requests
87
import sseclient
98

10-
logger = logging.getLogger(__name__)
11-
9+
from flagsmith.mappers import map_sse_event_to_stream_event
10+
from flagsmith.types import StreamEvent
1211

13-
class StreamEvent(pydantic.BaseModel):
14-
updated_at: pydantic.AwareDatetime
12+
logger = logging.getLogger(__name__)
1513

1614

1715
class EventStreamManager(threading.Thread):
@@ -40,9 +38,9 @@ def run(self) -> None:
4038
) as response:
4139
sse_client = sseclient.SSEClient(chunk for chunk in response)
4240
for event in sse_client.events():
43-
self.on_event(StreamEvent.model_validate_json(event.data))
41+
self.on_event(map_sse_event_to_stream_event(event))
4442

45-
except (requests.RequestException, pydantic.ValidationError):
43+
except (requests.RequestException, ValueError, TypeError):
4644
logger.exception("Error opening or reading from the event stream")
4745

4846
def stop(self) -> None:

flagsmith/types.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing
2+
from datetime import datetime
23

34
from flag_engine.engine import ContextValue
45
from typing_extensions import NotRequired, TypeAlias
@@ -17,6 +18,10 @@
1718
]
1819

1920

21+
class StreamEvent(typing.TypedDict):
22+
updated_at: datetime
23+
24+
2025
class TraitConfig(typing.TypedDict):
2126
value: ContextValue
2227
transient: bool

poetry.lock

Lines changed: 1 addition & 147 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ requests = "^2.32.3"
1515
requests-futures = "^1.0.1"
1616
flagsmith-flag-engine = { git = "https://github.com/Flagsmith/flagsmith-engine/", rev = "9ab732a20975fefa5996d89a2cbfd95ffb52c3d3" }
1717
sseclient-py = "^1.8.0"
18-
pydantic = "^2"
1918

2019
[tool.poetry.group.dev]
2120
optional = true
@@ -31,7 +30,6 @@ types-requests = "^2.32"
3130
pyfakefs = "^5.9.2"
3231

3332
[tool.mypy]
34-
plugins = ["pydantic.mypy"]
3533
exclude = ["example/*"]
3634

3735
[tool.black]

tests/test_streaming_manager.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,23 @@ def test_stream_manager_handles_timeout(
3737
def test_environment_updates_on_recent_event(
3838
server_api_key: str, mocker: MockerFixture
3939
) -> None:
40-
stream_updated_at = datetime(2020, 1, 1, 1, 1, 2)
40+
stream_updated_at = datetime(2020, 1, 1, 1, 1, 2, tzinfo=timezone.utc)
4141
environment_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc)
4242

4343
mocker.patch("flagsmith.Flagsmith.update_environment")
4444

4545
flagsmith = Flagsmith(environment_key=server_api_key)
4646
flagsmith._evaluation_context = MagicMock()
4747
flagsmith._environment_updated_at = environment_updated_at
48-
flagsmith.handle_stream_event(
49-
event=StreamEvent(updated_at=stream_updated_at.timestamp())
50-
)
48+
flagsmith.handle_stream_event(event=StreamEvent(updated_at=stream_updated_at))
5149
assert isinstance(flagsmith.update_environment, Mock)
5250
flagsmith.update_environment.assert_called_once()
5351

5452

5553
def test_environment_does_not_update_on_past_event(
5654
server_api_key: str, mocker: MockerFixture
5755
) -> None:
58-
stream_updated_at = datetime(2020, 1, 1, 1, 1, 1)
56+
stream_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc)
5957
environment_updated_at = datetime(2020, 1, 1, 1, 1, 2, tzinfo=timezone.utc)
6058

6159
mocker.patch("flagsmith.Flagsmith.update_environment")
@@ -64,17 +62,15 @@ def test_environment_does_not_update_on_past_event(
6462
flagsmith._evaluation_context = MagicMock()
6563
flagsmith._environment_updated_at = environment_updated_at
6664

67-
flagsmith.handle_stream_event(
68-
event=StreamEvent(updated_at=stream_updated_at.timestamp())
69-
)
65+
flagsmith.handle_stream_event(event=StreamEvent(updated_at=stream_updated_at))
7066
assert isinstance(flagsmith.update_environment, Mock)
7167
flagsmith.update_environment.assert_not_called()
7268

7369

7470
def test_environment_does_not_update_on_same_event(
7571
server_api_key: str, mocker: MockerFixture
7672
) -> None:
77-
stream_updated_at = datetime(2020, 1, 1, 1, 1, 1)
73+
stream_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc)
7874
environment_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc)
7975

8076
mocker.patch("flagsmith.Flagsmith.update_environment")
@@ -83,8 +79,6 @@ def test_environment_does_not_update_on_same_event(
8379
flagsmith._evaluation_context = MagicMock()
8480
flagsmith._environment_updated_at = environment_updated_at
8581

86-
flagsmith.handle_stream_event(
87-
event=StreamEvent(updated_at=stream_updated_at.timestamp())
88-
)
82+
flagsmith.handle_stream_event(event=StreamEvent(updated_at=stream_updated_at))
8983
assert isinstance(flagsmith.update_environment, Mock)
9084
flagsmith.update_environment.assert_not_called()

0 commit comments

Comments
 (0)