diff --git a/pyproject.toml b/pyproject.toml index 57548bfa..a9e4a46a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sap-cloud-sdk" -version = "0.34.0" +version = "0.35.0" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/src/sap_cloud_sdk/core/telemetry/config.py b/src/sap_cloud_sdk/core/telemetry/config.py index 3e151449..dbbe23b9 100644 --- a/src/sap_cloud_sdk/core/telemetry/config.py +++ b/src/sap_cloud_sdk/core/telemetry/config.py @@ -18,6 +18,7 @@ ATTR_SAP_SOLUTION_AREA, ATTR_MLFLOW_EXPERIMENT_ID, ATTR_SAP_ORD_ID, + ATTR_SAP_SERVICE_DISPLAY_NAME, SDK_NAME, ) @@ -35,6 +36,7 @@ ENV_SOLUTION_AREA = "SAP_SOLUTION_AREA" ENV_MLFLOW_EXPERIMENT_ID = "MLFLOW_EXPERIMENT_ID" ENV_ORD_DOCUMENT_ID = "ORD_DOCUMENT_ID" +ENV_SERVICE_DISPLAY_NAME = "SAP_SERVICE_DISPLAY_NAME" # OTEL environment variable keys ENV_OTLP_ENDPOINT = "OTEL_EXPORTER_OTLP_ENDPOINT" @@ -110,6 +112,16 @@ def _get_ord_id() -> Optional[str]: return value if value else None +def _get_service_display_name() -> Optional[str]: + """Get service display name from environment. + + Returns: + Value of SAP_SERVICE_DISPLAY_NAME, or None if the env var is missing or empty. + """ + value = os.getenv(ENV_SERVICE_DISPLAY_NAME) + return value if value else None + + def create_resource_attributes_from_env() -> dict: """Create OpenTelemetry Resource with SDK attributes. @@ -131,6 +143,7 @@ def create_resource_attributes_from_env() -> dict: - sap.solution_area (from SAP_SOLUTION_AREA, defaults to "unknown") - mlflow.experiment_id (from MLFLOW_EXPERIMENT_ID, omitted when unset or empty) - sap.ord.id (from ORD_DOCUMENT_ID, omitted when unset or empty) + - sap.service.display_name (from SAP_SERVICE_DISPLAY_NAME, omitted when unset or empty) """ attributes = { @@ -155,6 +168,10 @@ def create_resource_attributes_from_env() -> dict: if ord_id is not None: attributes[ATTR_SAP_ORD_ID] = ord_id + service_display_name = _get_service_display_name() + if service_display_name is not None: + attributes[ATTR_SAP_SERVICE_DISPLAY_NAME] = service_display_name + return attributes diff --git a/src/sap_cloud_sdk/core/telemetry/constants.py b/src/sap_cloud_sdk/core/telemetry/constants.py index 4be20d3f..875f00ed 100644 --- a/src/sap_cloud_sdk/core/telemetry/constants.py +++ b/src/sap_cloud_sdk/core/telemetry/constants.py @@ -32,6 +32,9 @@ # Attribute keys - ORD ATTR_SAP_ORD_ID = "sap.ord.id" +# Attribute keys - Service display name +ATTR_SAP_SERVICE_DISPLAY_NAME = "sap.service.display_name" + # Attribute keys - SAP App Foundation specific ATTR_CAPABILITY = "sap.cloud_sdk.capability" ATTR_FUNCTIONALITY = "sap.cloud_sdk.functionality" diff --git a/tests/core/unit/telemetry/test_config.py b/tests/core/unit/telemetry/test_config.py index ca3a770b..fe2f1f68 100644 --- a/tests/core/unit/telemetry/test_config.py +++ b/tests/core/unit/telemetry/test_config.py @@ -12,6 +12,7 @@ ATTR_MLFLOW_EXPERIMENT_ID, ATTR_SAP_SOLUTION_AREA, ATTR_SAP_ORD_ID, + ATTR_SAP_SERVICE_DISPLAY_NAME, ) @@ -266,3 +267,35 @@ def test_ord_id_independent_of_other_attributes(self): assert attrs[ATTR_SAP_ORD_ID] == "my-ord-id" assert attrs[ATTR_MLFLOW_EXPERIMENT_ID] == "exp-99" + + def test_service_display_name_omitted_when_unset(self): + """sap.service.display_name is omitted entirely when SAP_SERVICE_DISPLAY_NAME is unset.""" + with patch.dict('os.environ', {}, clear=True): + attrs = create_resource_attributes_from_env() + + assert ATTR_SAP_SERVICE_DISPLAY_NAME not in attrs + + def test_service_display_name_omitted_when_empty(self): + """sap.service.display_name is omitted when SAP_SERVICE_DISPLAY_NAME is an empty string.""" + with patch.dict('os.environ', {'SAP_SERVICE_DISPLAY_NAME': ''}, clear=True): + attrs = create_resource_attributes_from_env() + + assert ATTR_SAP_SERVICE_DISPLAY_NAME not in attrs + + def test_service_display_name_read_from_env(self): + """sap.service.display_name resource attribute is read from SAP_SERVICE_DISPLAY_NAME.""" + with patch.dict('os.environ', {'SAP_SERVICE_DISPLAY_NAME': 'My Service'}, clear=True): + attrs = create_resource_attributes_from_env() + + assert attrs[ATTR_SAP_SERVICE_DISPLAY_NAME] == "My Service" + + def test_service_display_name_independent_of_other_attributes(self): + """sap.service.display_name is populated independently from other optional attributes.""" + with patch.dict('os.environ', { + 'SAP_SERVICE_DISPLAY_NAME': 'My Service', + 'ORD_DOCUMENT_ID': 'my-ord-id', + }, clear=True): + attrs = create_resource_attributes_from_env() + + assert attrs[ATTR_SAP_SERVICE_DISPLAY_NAME] == "My Service" + assert attrs[ATTR_SAP_ORD_ID] == "my-ord-id" diff --git a/uv.lock b/uv.lock index 7809a892..92e88dcb 100644 --- a/uv.lock +++ b/uv.lock @@ -3685,7 +3685,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.33.2" +version = "0.35.0" source = { editable = "." } dependencies = [ { name = "grpcio" }, @@ -3715,6 +3715,9 @@ extensibility = [ langchain = [ { name = "langchain-core" }, ] +langgraph = [ + { name = "langgraph" }, +] starlette = [ { name = "starlette" }, ] @@ -3747,6 +3750,7 @@ requires-dist = [ { name = "hatchling", specifier = "~=1.27.0" }, { name = "httpx", specifier = ">=0.27.0" }, { name = "langchain-core", marker = "extra == 'langchain'", specifier = ">=1.2.7" }, + { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=1.0.0" }, { name = "mcp", specifier = ">=1.1.0" }, { name = "minio", specifier = "~=7.2.16" }, { name = "opentelemetry-api", specifier = ">=1.42.1" }, @@ -3764,7 +3768,7 @@ requires-dist = [ { name = "starlette", marker = "extra == 'starlette'", specifier = ">=0.40.0" }, { name = "traceloop-sdk", specifier = "~=0.61.0" }, ] -provides-extras = ["extensibility", "starlette", "langchain"] +provides-extras = ["extensibility", "starlette", "langchain", "langgraph"] [package.metadata.requires-dev] dev = [