From 6ea38abb9b53ceee3c43177c16a505de684a9a37 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Wed, 3 Jun 2026 08:56:10 -0300 Subject: [PATCH 1/3] trying to fix and adding debug file --- debug_k8s_telemetry.py | 176 ++++++++++++++++++ .../core/telemetry/auto_instrument.py | 5 + 2 files changed, 181 insertions(+) create mode 100644 debug_k8s_telemetry.py diff --git a/debug_k8s_telemetry.py b/debug_k8s_telemetry.py new file mode 100644 index 00000000..80bca09a --- /dev/null +++ b/debug_k8s_telemetry.py @@ -0,0 +1,176 @@ +""" +Reproduces the k8s autoinstrumentation resource-attribute bug with a real LangChain agent. + +What this simulates +------------------- +On k8s, the OTel operator runs sitecustomize.py at pod startup (before any app +code), which: + 1. Creates a TracerProvider whose resource carries telemetry.auto.version. + 2. Calls LangChainInstrumentor().instrument() — this calls provider.get_tracer() + and caches a Tracer that snapshots provider.resource at that instant. + +The app then calls auto_instrument(), which merges SAP resource attrs onto +provider._resource — but the cached LangChain Tracer still holds the old object. +All LangChain spans therefore carry the operator resource only, never SAP attrs. + +Run +--- + .venv/bin/python debug_k8s_telemetry.py + +Output +------ + spans_debug.json — every finished span with its resource attributes + Console — PASS / FAIL per span with the attrs that matter + +Exit code 0 = all spans carry SAP attrs (bug fixed) +Exit code 1 = some spans are missing SAP attrs (bug present) +""" + +import json +import logging +import os +import sys + +logging.basicConfig(level=logging.INFO, format="%(name)s %(levelname)s %(message)s") + +# ── env: pretend we are running in a k8s pod ───────────────────────────────── +os.environ.update({ + "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317", + "APPFND_CONHOS_APP_NAME": "my-agent", + "APPFND_CONHOS_ENVIRONMENT": "dev", + "APPFND_CONHOS_REGION": "eu10", + "APPFND_CONHOS_SUBACCOUNTID": "sub-abc-123", + "APPFND_CONHOS_SYSTEM_ROLE": "ZAFT", + "SAP_SOLUTION_AREA": "AFND", +}) + +# ── imports that must happen before anything touches the global provider ────── +from opentelemetry import trace +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import SimpleSpanProcessor +from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter +from opentelemetry.instrumentation.langchain import LangchainInstrumentor + +from langchain_core.messages import HumanMessage, AIMessage +from langchain_core.language_models.chat_models import BaseChatModel +from langchain_core.outputs import ChatResult, ChatGeneration + +# ── fake LLM so we need no API key ─────────────────────────────────────────── +class _EchoLLM(BaseChatModel): + """Instantly echoes the last human message back as an AI message.""" + + @property + def _llm_type(self) -> str: + return "echo" + + def _generate(self, messages, stop=None, run_manager=None, **kwargs): + text = messages[-1].content if messages else "hello" + return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))]) + + +# ═══════════════════════════════════════════════════════════════════════════════ +# PHASE 1 — simulate the OTel operator's sitecustomize.py +# ═══════════════════════════════════════════════════════════════════════════════ +print("\n── Phase 1: operator sitecustomize.py ──────────────────────────────────") + +span_exporter = InMemorySpanExporter() + +operator_provider = TracerProvider( + resource=Resource.create({ + "telemetry.auto.version": "0.62b1", + "k8s.deployment.name": "my-agent-deployment", + "k8s.namespace.name": "prod", + "service.name": "my-agent-deployment", # operator-derived, should be overridden + }) +) +operator_provider.add_span_processor(SimpleSpanProcessor(span_exporter)) +trace.set_tracer_provider(operator_provider) +print(" [operator] TracerProvider installed with telemetry.auto.version=0.62b1") + +# The operator calls LangChainInstrumentor().instrument() — this internally +# calls provider.get_tracer() and caches the Tracer with the current resource. +LangchainInstrumentor().instrument() +print(" [operator] LangchainInstrumentor().instrument() done") +print(f" [operator] provider._tracers cache now has {len(operator_provider._tracers)} tracer(s)") + +# ═══════════════════════════════════════════════════════════════════════════════ +# PHASE 2 — application startup: call auto_instrument() +# ═══════════════════════════════════════════════════════════════════════════════ +print("\n── Phase 2: app calls auto_instrument() ────────────────────────────────") + +# We patch Traceloop.init so it doesn't try to connect to a real collector, +# but we let _merge_resource_attrs_into_active_provider_if_wrapper_installed +# run against the live operator_provider — that's the code under test. +from unittest.mock import patch +with patch("sap_cloud_sdk.core.telemetry.auto_instrument.Traceloop"), \ + patch("sap_cloud_sdk.core.telemetry.auto_instrument.GRPCSpanExporter"), \ + patch("sap_cloud_sdk.core.telemetry.auto_instrument.GenAIAttributeTransformer"): + from sap_cloud_sdk.core.telemetry import auto_instrument + auto_instrument() + +print(f" [sdk] provider._resource after merge: {list(operator_provider.resource.attributes.keys())}") +print(f" [sdk] provider._tracers cache still has {len(operator_provider._tracers)} tracer(s)") + +# ═══════════════════════════════════════════════════════════════════════════════ +# PHASE 3 — run a tiny LangChain chain (the agent's "work") +# ═══════════════════════════════════════════════════════════════════════════════ +print("\n── Phase 3: agent invokes LangChain chain ──────────────────────────────") + +llm = _EchoLLM() +response = llm.invoke([HumanMessage(content="What is the capital of France?")]) +print(f" [agent] LLM response: {response.content!r}") + +# ═══════════════════════════════════════════════════════════════════════════════ +# PHASE 4 — collect spans, validate, write to file +# ═══════════════════════════════════════════════════════════════════════════════ +print("\n── Phase 4: span validation ────────────────────────────────────────────") + +SAP_ATTRS = [ + "sap.cloud_sdk.name", + "sap.cloud_sdk.language", + "sap.cloud_sdk.version", + "sap.solution_area", + "sap.cld.subaccount_id", +] + +finished = span_exporter.get_finished_spans() +output = [] +all_ok = True + +for span in finished: + attrs = dict(span.resource.attributes) + missing = [k for k in SAP_ATTRS if k not in attrs] + ok = len(missing) == 0 + all_ok = all_ok and ok + + entry = { + "span_name": span.name, + "instrumentation": span.instrumentation_scope.name if span.instrumentation_scope else "unknown", + "status": "PASS" if ok else "FAIL", + "missing_sap_attrs": missing, + "resource_attributes": attrs, + } + output.append(entry) + + status_label = "PASS" if ok else "FAIL" + print(f" [{status_label}] {span.name!r} ({entry['instrumentation']})") + if missing: + print(f" missing: {missing}") + else: + print(f" sap.cloud_sdk.name = {attrs.get('sap.cloud_sdk.name')!r}") + print(f" service.name = {attrs.get('service.name')!r}") + +out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "spans_debug.json") +with open(out_path, "w") as f: + json.dump(output, f, indent=2) + +print(f"\n Wrote {len(output)} span(s) to {out_path}") + +if all_ok: + print("\nRESULT: PASS — all spans carry SAP resource attributes.\n") + sys.exit(0) +else: + failed = sum(1 for e in output if e["status"] == "FAIL") + print(f"\nRESULT: FAIL — {failed}/{len(output)} span(s) missing SAP resource attributes.\n") + sys.exit(1) diff --git a/src/sap_cloud_sdk/core/telemetry/auto_instrument.py b/src/sap_cloud_sdk/core/telemetry/auto_instrument.py index 59f78e59..c87d7770 100644 --- a/src/sap_cloud_sdk/core/telemetry/auto_instrument.py +++ b/src/sap_cloud_sdk/core/telemetry/auto_instrument.py @@ -170,6 +170,11 @@ def _merge_resource_attrs_into_active_provider_if_wrapper_installed( return provider._resource = provider.resource.merge(Resource.create(sap_attrs)) + + with provider._tracers_lock: + for tracer in provider._tracers.values(): + tracer.resource = provider._resource + logger.info( "Merged sap-cloud-sdk resource attrs onto wrapper-installed " "TracerProvider (marker: telemetry.auto.version)" From af681bac02d9393bb90deba8813dcf66631a69d0 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Tue, 9 Jun 2026 10:11:27 -0300 Subject: [PATCH 2/3] removed test file --- debug_k8s_telemetry.py | 176 ----------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 debug_k8s_telemetry.py diff --git a/debug_k8s_telemetry.py b/debug_k8s_telemetry.py deleted file mode 100644 index 80bca09a..00000000 --- a/debug_k8s_telemetry.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Reproduces the k8s autoinstrumentation resource-attribute bug with a real LangChain agent. - -What this simulates -------------------- -On k8s, the OTel operator runs sitecustomize.py at pod startup (before any app -code), which: - 1. Creates a TracerProvider whose resource carries telemetry.auto.version. - 2. Calls LangChainInstrumentor().instrument() — this calls provider.get_tracer() - and caches a Tracer that snapshots provider.resource at that instant. - -The app then calls auto_instrument(), which merges SAP resource attrs onto -provider._resource — but the cached LangChain Tracer still holds the old object. -All LangChain spans therefore carry the operator resource only, never SAP attrs. - -Run ---- - .venv/bin/python debug_k8s_telemetry.py - -Output ------- - spans_debug.json — every finished span with its resource attributes - Console — PASS / FAIL per span with the attrs that matter - -Exit code 0 = all spans carry SAP attrs (bug fixed) -Exit code 1 = some spans are missing SAP attrs (bug present) -""" - -import json -import logging -import os -import sys - -logging.basicConfig(level=logging.INFO, format="%(name)s %(levelname)s %(message)s") - -# ── env: pretend we are running in a k8s pod ───────────────────────────────── -os.environ.update({ - "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317", - "APPFND_CONHOS_APP_NAME": "my-agent", - "APPFND_CONHOS_ENVIRONMENT": "dev", - "APPFND_CONHOS_REGION": "eu10", - "APPFND_CONHOS_SUBACCOUNTID": "sub-abc-123", - "APPFND_CONHOS_SYSTEM_ROLE": "ZAFT", - "SAP_SOLUTION_AREA": "AFND", -}) - -# ── imports that must happen before anything touches the global provider ────── -from opentelemetry import trace -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import SimpleSpanProcessor -from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter -from opentelemetry.instrumentation.langchain import LangchainInstrumentor - -from langchain_core.messages import HumanMessage, AIMessage -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.outputs import ChatResult, ChatGeneration - -# ── fake LLM so we need no API key ─────────────────────────────────────────── -class _EchoLLM(BaseChatModel): - """Instantly echoes the last human message back as an AI message.""" - - @property - def _llm_type(self) -> str: - return "echo" - - def _generate(self, messages, stop=None, run_manager=None, **kwargs): - text = messages[-1].content if messages else "hello" - return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))]) - - -# ═══════════════════════════════════════════════════════════════════════════════ -# PHASE 1 — simulate the OTel operator's sitecustomize.py -# ═══════════════════════════════════════════════════════════════════════════════ -print("\n── Phase 1: operator sitecustomize.py ──────────────────────────────────") - -span_exporter = InMemorySpanExporter() - -operator_provider = TracerProvider( - resource=Resource.create({ - "telemetry.auto.version": "0.62b1", - "k8s.deployment.name": "my-agent-deployment", - "k8s.namespace.name": "prod", - "service.name": "my-agent-deployment", # operator-derived, should be overridden - }) -) -operator_provider.add_span_processor(SimpleSpanProcessor(span_exporter)) -trace.set_tracer_provider(operator_provider) -print(" [operator] TracerProvider installed with telemetry.auto.version=0.62b1") - -# The operator calls LangChainInstrumentor().instrument() — this internally -# calls provider.get_tracer() and caches the Tracer with the current resource. -LangchainInstrumentor().instrument() -print(" [operator] LangchainInstrumentor().instrument() done") -print(f" [operator] provider._tracers cache now has {len(operator_provider._tracers)} tracer(s)") - -# ═══════════════════════════════════════════════════════════════════════════════ -# PHASE 2 — application startup: call auto_instrument() -# ═══════════════════════════════════════════════════════════════════════════════ -print("\n── Phase 2: app calls auto_instrument() ────────────────────────────────") - -# We patch Traceloop.init so it doesn't try to connect to a real collector, -# but we let _merge_resource_attrs_into_active_provider_if_wrapper_installed -# run against the live operator_provider — that's the code under test. -from unittest.mock import patch -with patch("sap_cloud_sdk.core.telemetry.auto_instrument.Traceloop"), \ - patch("sap_cloud_sdk.core.telemetry.auto_instrument.GRPCSpanExporter"), \ - patch("sap_cloud_sdk.core.telemetry.auto_instrument.GenAIAttributeTransformer"): - from sap_cloud_sdk.core.telemetry import auto_instrument - auto_instrument() - -print(f" [sdk] provider._resource after merge: {list(operator_provider.resource.attributes.keys())}") -print(f" [sdk] provider._tracers cache still has {len(operator_provider._tracers)} tracer(s)") - -# ═══════════════════════════════════════════════════════════════════════════════ -# PHASE 3 — run a tiny LangChain chain (the agent's "work") -# ═══════════════════════════════════════════════════════════════════════════════ -print("\n── Phase 3: agent invokes LangChain chain ──────────────────────────────") - -llm = _EchoLLM() -response = llm.invoke([HumanMessage(content="What is the capital of France?")]) -print(f" [agent] LLM response: {response.content!r}") - -# ═══════════════════════════════════════════════════════════════════════════════ -# PHASE 4 — collect spans, validate, write to file -# ═══════════════════════════════════════════════════════════════════════════════ -print("\n── Phase 4: span validation ────────────────────────────────────────────") - -SAP_ATTRS = [ - "sap.cloud_sdk.name", - "sap.cloud_sdk.language", - "sap.cloud_sdk.version", - "sap.solution_area", - "sap.cld.subaccount_id", -] - -finished = span_exporter.get_finished_spans() -output = [] -all_ok = True - -for span in finished: - attrs = dict(span.resource.attributes) - missing = [k for k in SAP_ATTRS if k not in attrs] - ok = len(missing) == 0 - all_ok = all_ok and ok - - entry = { - "span_name": span.name, - "instrumentation": span.instrumentation_scope.name if span.instrumentation_scope else "unknown", - "status": "PASS" if ok else "FAIL", - "missing_sap_attrs": missing, - "resource_attributes": attrs, - } - output.append(entry) - - status_label = "PASS" if ok else "FAIL" - print(f" [{status_label}] {span.name!r} ({entry['instrumentation']})") - if missing: - print(f" missing: {missing}") - else: - print(f" sap.cloud_sdk.name = {attrs.get('sap.cloud_sdk.name')!r}") - print(f" service.name = {attrs.get('service.name')!r}") - -out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "spans_debug.json") -with open(out_path, "w") as f: - json.dump(output, f, indent=2) - -print(f"\n Wrote {len(output)} span(s) to {out_path}") - -if all_ok: - print("\nRESULT: PASS — all spans carry SAP resource attributes.\n") - sys.exit(0) -else: - failed = sum(1 for e in output if e["status"] == "FAIL") - print(f"\nRESULT: FAIL — {failed}/{len(output)} span(s) missing SAP resource attributes.\n") - sys.exit(1) From 644a58e3d8a607f79c241bc61a5abf11bff4c3c5 Mon Sep 17 00:00:00 2001 From: Lucas Soares Date: Tue, 9 Jun 2026 23:05:48 -0300 Subject: [PATCH 3/3] Version bump --- pyproject.toml | 2 +- uv.lock | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6ff98838..35a660e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sap-cloud-sdk" -version = "0.25.0" +version = "0.25.1" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/uv.lock b/uv.lock index 7839170a..d0a39812 100644 --- a/uv.lock +++ b/uv.lock @@ -909,9 +909,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/8fd452fd81adb9ec79c8275c1375702ab0fd6bee4952da12eaa09b9508d8/greenlet-3.5.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ebeb75c81211f5c702576cf81f315e77e23cfdb2c7c6fcb9dd143e6de35c360", size = 623515, upload-time = "2026-05-20T14:09:07.853Z" }, { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bc/c318aa9f3ffc77320fddcee3d892be957b42e2ff947198d9450b004f3a38/greenlet-3.5.1-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:017a544f0385d441e88714160d089d6900ef46c9eff9d99b6715a5ef2d127747", size = 418439, upload-time = "2026-05-20T14:01:38.446Z" }, { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, @@ -919,9 +917,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, - { url = "https://files.pythonhosted.org/packages/7c/6c/de5b1b388cd2d9fbdfeab324863daba37d54e6e233ddbefd70b385a8c591/greenlet-3.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89101bfd5011e069be974903cb3a4e4523845e4ece2d62dcd8d358933c0ef249", size = 620094, upload-time = "2026-05-20T14:09:09.18Z" }, { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, - { url = "https://files.pythonhosted.org/packages/4a/43/1204baffab8a6476464795a7ccf394a3248d4f22c9f87173a15b36b6d971/greenlet-3.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:e6cd99ea59dd5d89f0c956606571d79bfe6f68c9eb7f4a4083a41a7f1587edee", size = 422782, upload-time = "2026-05-20T14:01:39.597Z" }, { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, @@ -929,9 +925,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, - { url = "https://files.pythonhosted.org/packages/19/ba/c24110c55dffa55aa6e1d98b45310da33801aeba7686ff0190fe5d46fd32/greenlet-3.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce", size = 622911, upload-time = "2026-05-20T14:09:10.598Z" }, { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, - { url = "https://files.pythonhosted.org/packages/ec/7b/d20db2e8a5ad6c038702f3179b136f93f0a3d1a21a0c0777f3e470cdf4b2/greenlet-3.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436", size = 425228, upload-time = "2026-05-20T14:01:40.837Z" }, { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, @@ -939,9 +933,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/2d80842910da44f78c286532d084b8a5c3717c844ae80ceb3858738ae89a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c", size = 667767, upload-time = "2026-05-20T14:09:12.15Z" }, { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d3/dad2eecedfbb1ed7050a20dcfae40c1442b74bc7423608be2c7e03ee7133/greenlet-3.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d", size = 470786, upload-time = "2026-05-20T14:01:42.064Z" }, { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, @@ -949,18 +941,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, - { url = "https://files.pythonhosted.org/packages/8c/46/5987dcd1a2570ba84f3b187536b2ca3ae97613387e57f5cfa99df068fe5e/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f", size = 656607, upload-time = "2026-05-20T14:09:13.949Z" }, { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c1/6da0a9ddcc29d7e51ef14883fa3dc1e53b3f4ffba00582106c7bf55da1d8/greenlet-3.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de", size = 488287, upload-time = "2026-05-20T14:01:43.143Z" }, { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, - { url = "https://files.pythonhosted.org/packages/dc/74/807a047255bf1e09303627c46dc043dca596b6958a354d904f32ab382005/greenlet-3.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0", size = 672962, upload-time = "2026-05-20T14:09:15.532Z" }, { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, - { url = "https://files.pythonhosted.org/packages/76/32/19d4e13225193c29b13e308015223f7d75fd3d8623d49dd19040d2ce8ec1/greenlet-3.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc", size = 476047, upload-time = "2026-05-20T14:01:44.39Z" }, { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, @@ -968,9 +956,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, - { url = "https://files.pythonhosted.org/packages/c9/9d/1dcdf7b95ab3cf8c7b6d7277c18a5e167312f2b362ddfcc5d5e6d8d84b43/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c", size = 659998, upload-time = "2026-05-20T14:09:16.912Z" }, { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/c4959664fc231d587d66d8e81f2095e98056ba1954beafdcbe635e251052/greenlet-3.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62", size = 494470, upload-time = "2026-05-20T14:01:45.611Z" }, { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, @@ -3696,7 +3682,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.25.0" +version = "0.25.1" source = { editable = "." } dependencies = [ { name = "grpcio" },