Skip to content

Commit 441c46a

Browse files
authored
feat: Support SDK metrics (#136)
1 parent 9ac4dc7 commit 441c46a

File tree

9 files changed

+292
-27
lines changed

9 files changed

+292
-27
lines changed

.github/workflows/pytest.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,9 @@ jobs:
3232
pip install poetry
3333
poetry install --with dev
3434
35+
- name: Check for new typing errors
36+
if: ${{ matrix.python-version != '3.8' }}
37+
run: poetry run mypy --strict .
38+
3539
- name: Run Tests
3640
run: poetry run pytest

.pre-commit-config.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
repos:
2-
- repo: https://github.com/pre-commit/mirrors-mypy
3-
rev: v1.16.1
4-
hooks:
5-
- id: mypy
6-
args: [--strict]
7-
additional_dependencies:
8-
[pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py]
92
- repo: https://github.com/PyCQA/isort
103
rev: 6.0.1
114
hooks:

flagsmith/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from . import webhooks
2-
from .flagsmith import Flagsmith
1+
from flagsmith import webhooks
2+
from flagsmith.flagsmith import Flagsmith
3+
from flagsmith.version import __version__
34

4-
__all__ = ("Flagsmith", "webhooks")
5+
__all__ = ("Flagsmith", "webhooks", "__version__")

flagsmith/flagsmith.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import sys
23
import typing
34
from datetime import timezone
45

@@ -11,6 +12,7 @@
1112
from flag_engine.identities.traits.types import TraitValue
1213
from flag_engine.segments.evaluator import get_identity_segments
1314
from requests.adapters import HTTPAdapter
15+
from requests.utils import default_user_agent
1416
from urllib3 import Retry
1517

1618
from flagsmith.analytics import AnalyticsProcessor
@@ -19,13 +21,24 @@
1921
from flagsmith.offline_handlers import BaseOfflineHandler
2022
from flagsmith.polling_manager import EnvironmentDataPollingManager
2123
from flagsmith.streaming_manager import EventStreamManager, StreamEvent
22-
from flagsmith.types import JsonType, TraitConfig, TraitMapping
24+
from flagsmith.types import (
25+
ApplicationMetadata,
26+
JsonType,
27+
TraitConfig,
28+
TraitMapping,
29+
)
2330
from flagsmith.utils.identities import generate_identity_data
31+
from flagsmith.version import __version__
2432

2533
logger = logging.getLogger(__name__)
2634

2735
DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/"
2836
DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/"
37+
DEFAULT_USER_AGENT = (
38+
f"flagsmith-python-client/{__version__} "
39+
+ default_user_agent()
40+
+ f" python/{sys.version_info.major}.{sys.version_info.minor}"
41+
)
2942

3043

3144
class Flagsmith:
@@ -61,6 +74,7 @@ def __init__(
6174
offline_mode: bool = False,
6275
offline_handler: typing.Optional[BaseOfflineHandler] = None,
6376
enable_realtime_updates: bool = False,
77+
application_metadata: typing.Optional[ApplicationMetadata] = None,
6478
):
6579
"""
6680
:param environment_key: The environment key obtained from Flagsmith interface.
@@ -88,6 +102,7 @@ def __init__(
88102
document from another source when in offline_mode. Works in place of
89103
default_flag_handler if offline_mode is not set and using remote evaluation.
90104
:param enable_realtime_updates: Use real-time functionality via SSE as opposed to polling the API
105+
:param application_metadata: Optional metadata about the client application.
91106
"""
92107

93108
self.offline_mode = offline_mode
@@ -122,7 +137,11 @@ def __init__(
122137

123138
self.session = requests.Session()
124139
self.session.headers.update(
125-
**{"X-Environment-Key": environment_key}, **(custom_headers or {})
140+
self._get_headers(
141+
environment_key=environment_key,
142+
application_metadata=application_metadata,
143+
custom_headers=custom_headers,
144+
)
126145
)
127146
self.session.proxies.update(proxies or {})
128147
retries = retries or Retry(total=3, backoff_factor=0.1)
@@ -275,6 +294,24 @@ def update_environment(self) -> None:
275294
identity.identifier: identity for identity in overrides
276295
}
277296

297+
def _get_headers(
298+
self,
299+
environment_key: str,
300+
application_metadata: typing.Optional[ApplicationMetadata],
301+
custom_headers: typing.Optional[typing.Dict[str, typing.Any]],
302+
) -> typing.Dict[str, str]:
303+
headers = {
304+
"X-Environment-Key": environment_key,
305+
"User-Agent": DEFAULT_USER_AGENT,
306+
}
307+
if application_metadata:
308+
if name := application_metadata.get("name"):
309+
headers["Flagsmith-Application-Name"] = name
310+
if version := application_metadata.get("version"):
311+
headers["Flagsmith-Application-Version"] = version
312+
headers.update(custom_headers or {})
313+
return headers
314+
278315
def _get_environment_from_api(self) -> EnvironmentModel:
279316
environment_data = self._get_json_response(self.environment_url, method="GET")
280317
return EnvironmentModel.model_validate(environment_data)

flagsmith/types.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typing
22

33
from flag_engine.identities.traits.types import TraitValue
4-
from typing_extensions import TypeAlias
4+
from typing_extensions import NotRequired, TypeAlias
55

66
_JsonScalarType: TypeAlias = typing.Union[
77
int,
@@ -23,3 +23,8 @@ class TraitConfig(typing.TypedDict):
2323

2424

2525
TraitMapping: TypeAlias = typing.Mapping[str, typing.Union[TraitValue, TraitConfig]]
26+
27+
28+
class ApplicationMetadata(typing.TypedDict):
29+
name: NotRequired[str]
30+
version: NotRequired[str]

flagsmith/version.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from importlib.metadata import version
2+
3+
__version__ = version("flagsmith")

0 commit comments

Comments
 (0)