|
1 | 1 | import logging |
| 2 | +import sys |
2 | 3 | import typing |
3 | 4 | from datetime import timezone |
4 | 5 |
|
|
11 | 12 | from flag_engine.identities.traits.types import TraitValue |
12 | 13 | from flag_engine.segments.evaluator import get_identity_segments |
13 | 14 | from requests.adapters import HTTPAdapter |
| 15 | +from requests.utils import default_user_agent |
14 | 16 | from urllib3 import Retry |
15 | 17 |
|
16 | 18 | from flagsmith.analytics import AnalyticsProcessor |
|
19 | 21 | from flagsmith.offline_handlers import BaseOfflineHandler |
20 | 22 | from flagsmith.polling_manager import EnvironmentDataPollingManager |
21 | 23 | 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 | +) |
23 | 30 | from flagsmith.utils.identities import generate_identity_data |
| 31 | +from flagsmith.version import __version__ |
24 | 32 |
|
25 | 33 | logger = logging.getLogger(__name__) |
26 | 34 |
|
27 | 35 | DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" |
28 | 36 | 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 | +) |
29 | 42 |
|
30 | 43 |
|
31 | 44 | class Flagsmith: |
@@ -61,6 +74,7 @@ def __init__( |
61 | 74 | offline_mode: bool = False, |
62 | 75 | offline_handler: typing.Optional[BaseOfflineHandler] = None, |
63 | 76 | enable_realtime_updates: bool = False, |
| 77 | + application_metadata: typing.Optional[ApplicationMetadata] = None, |
64 | 78 | ): |
65 | 79 | """ |
66 | 80 | :param environment_key: The environment key obtained from Flagsmith interface. |
@@ -88,6 +102,7 @@ def __init__( |
88 | 102 | document from another source when in offline_mode. Works in place of |
89 | 103 | default_flag_handler if offline_mode is not set and using remote evaluation. |
90 | 104 | :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. |
91 | 106 | """ |
92 | 107 |
|
93 | 108 | self.offline_mode = offline_mode |
@@ -122,7 +137,11 @@ def __init__( |
122 | 137 |
|
123 | 138 | self.session = requests.Session() |
124 | 139 | 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 | + ) |
126 | 145 | ) |
127 | 146 | self.session.proxies.update(proxies or {}) |
128 | 147 | retries = retries or Retry(total=3, backoff_factor=0.1) |
@@ -275,6 +294,24 @@ def update_environment(self) -> None: |
275 | 294 | identity.identifier: identity for identity in overrides |
276 | 295 | } |
277 | 296 |
|
| 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 | + |
278 | 315 | def _get_environment_from_api(self) -> EnvironmentModel: |
279 | 316 | environment_data = self._get_json_response(self.environment_url, method="GET") |
280 | 317 | return EnvironmentModel.model_validate(environment_data) |
|
0 commit comments