Repository: Unleash/unleash-client-python
Pull request #317 (2024-07-25 14:50:49)

feat: add metrics metadata
Adds metadata to registration and metrics, this doesn't affect the public API or internal workings, really, it just allows the Unleash server to get better insights into what the connected SDKs are up to in the wild


Patch:
diff --git a/UnleashClient/api/register.py b/UnleashClient/api/register.py
index 19f365d..dc15306 100644
--- a/UnleashClient/api/register.py
+++ b/UnleashClient/api/register.py
@@ -1,11 +1,13 @@
 import json
 from datetime import datetime, timezone
+from platform import python_implementation, python_version
 
 import requests
 from requests.exceptions import InvalidHeader, InvalidSchema, InvalidURL, MissingSchema
 
 from UnleashClient.constants import (
     APPLICATION_HEADERS,
+    CLIENT_SPEC_VERSION,
     REGISTER_URL,
     SDK_NAME,
     SDK_VERSION,
@@ -41,22 +43,26 @@ def register_client(
     :param request_timeout:
     :return: true if registration successful, false if registration unsuccessful or exception.
     """
-    registation_request = {
+    registration_request = {
         "appName": app_name,
         "instanceId": instance_id,
         "sdkVersion": f"{SDK_NAME}:{SDK_VERSION}",
         "strategies": [*supported_strategies],
         "started": datetime.now(timezone.utc).isoformat(),
         "interval": metrics_interval,
+        "platformName": python_implementation(),
+        "platformVersion": python_version(),
+        "yggdrasilVersion": None,
+        "specVersion": CLIENT_SPEC_VERSION,
     }
 
     try:
         LOGGER.info("Registering unleash client with unleash @ %s", url)
-        LOGGER.info("Registration request information: %s", registation_request)
+        LOGGER.info("Registration request information: %s", registration_request)
 
         resp = requests.post(
             url + REGISTER_URL,
-            data=json.dumps(registation_request),
+            data=json.dumps(registration_request),
             headers={**custom_headers, **APPLICATION_HEADERS},
             timeout=request_timeout,
             **custom_options,
diff --git a/UnleashClient/periodic_tasks/send_metrics.py b/UnleashClient/periodic_tasks/send_metrics.py
index b1634e7..e6076c4 100644
--- a/UnleashClient/periodic_tasks/send_metrics.py
+++ b/UnleashClient/periodic_tasks/send_metrics.py
@@ -1,9 +1,10 @@
 from collections import ChainMap
 from datetime import datetime, timezone
+from platform import python_implementation, python_version
 
 from UnleashClient.api import send_metrics
 from UnleashClient.cache import BaseCache
-from UnleashClient.constants import METRIC_LAST_SENT_TIME
+from UnleashClient.constants import CLIENT_SPEC_VERSION, METRIC_LAST_SENT_TIME
 from UnleashClient.utils import LOGGER
 
 
@@ -52,6 +53,10 @@ def aggregate_and_send_metrics(
             "stop": datetime.now(timezone.utc).isoformat(),
             "toggles": feature_stats_dict,
         },
+        "platformName": python_implementation(),
+        "platformVersion": python_version(),
+        "yggdrasilVersion": None,
+        "specVersion": CLIENT_SPEC_VERSION,
     }
 
     if feature_stats_dict:


Test patch:
diff --git a/tests/unit_tests/api/test_register.py b/tests/unit_tests/api/test_register.py
index 7ee670a..6f5cf4e 100644
--- a/tests/unit_tests/api/test_register.py
+++ b/tests/unit_tests/api/test_register.py
@@ -1,3 +1,5 @@
+import json
+
 import responses
 from pytest import mark, param
 from requests import ConnectionError
@@ -13,7 +15,7 @@ from tests.utilities.testing_constants import (
     URL,
 )
 from UnleashClient.api import register_client
-from UnleashClient.constants import REGISTER_URL
+from UnleashClient.constants import CLIENT_SPEC_VERSION, REGISTER_URL
 
 FULL_REGISTER_URL = URL + REGISTER_URL
 
@@ -48,3 +50,27 @@ def test_register_client(payload, status, expected):
 
     assert len(responses.calls) == 1
     assert result is expected
+
+
+@responses.activate
+def test_register_includes_metadata():
+    responses.add(responses.POST, FULL_REGISTER_URL, json={}, status=202)
+
+    register_client(
+        URL,
+        APP_NAME,
+        INSTANCE_ID,
+        METRICS_INTERVAL,
+        CUSTOM_HEADERS,
+        CUSTOM_OPTIONS,
+        DEFAULT_STRATEGY_MAPPING,
+        REQUEST_TIMEOUT,
+    )
+
+    assert len(responses.calls) == 1
+    request = json.loads(responses.calls[0].request.body)
+
+    assert request["yggdrasilVersion"] is None
+    assert request["specVersion"] == CLIENT_SPEC_VERSION
+    assert request["platformName"] is not None
+    assert request["platformVersion"] is not None
diff --git a/tests/unit_tests/periodic/test_aggregate_and_send_metrics.py b/tests/unit_tests/periodic/test_aggregate_and_send_metrics.py
index 25dd36d..edb67e7 100644
--- a/tests/unit_tests/periodic/test_aggregate_and_send_metrics.py
+++ b/tests/unit_tests/periodic/test_aggregate_and_send_metrics.py
@@ -14,7 +14,11 @@ from tests.utilities.testing_constants import (
     URL,
 )
 from UnleashClient.cache import FileCache
-from UnleashClient.constants import METRIC_LAST_SENT_TIME, METRICS_URL
+from UnleashClient.constants import (
+    CLIENT_SPEC_VERSION,
+    METRIC_LAST_SENT_TIME,
+    METRICS_URL,
+)
 from UnleashClient.features import Feature
 from UnleashClient.periodic_tasks import aggregate_and_send_metrics
 from UnleashClient.strategies import Default, RemoteAddress
@@ -107,3 +111,34 @@ def test_no_metrics():
     )
 
     assert len(responses.calls) == 0
+
+
+@responses.activate
+def test_metrics_metadata_is_sent():
+    responses.add(responses.POST, FULL_METRICS_URL, json={}, status=200)
+
+    cache = FileCache("TestCache")
+
+    to_make_sure_metrics_fires = Feature("My Feature1", True, None)
+    to_make_sure_metrics_fires.yes_count = 1
+
+    features = {"Something": to_make_sure_metrics_fires}
+
+    aggregate_and_send_metrics(
+        URL,
+        APP_NAME,
+        INSTANCE_ID,
+        CUSTOM_HEADERS,
+        CUSTOM_OPTIONS,
+        features,
+        cache,
+        REQUEST_TIMEOUT,
+    )
+
+    assert len(responses.calls) == 1
+    request = json.loads(responses.calls[0].request.body)
+
+    assert request["yggdrasilVersion"] is None
+    assert request["specVersion"] == CLIENT_SPEC_VERSION
+    assert request["platformName"] is not None
+    assert request["platformVersion"] is not None