diff --git a/pyproject.toml b/pyproject.toml index 52ece028..c15ed3e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,6 @@ [project] name = "sap-cloud-sdk" - -version = "0.49.0" +version = "0.49.1" description = "SAP Cloud SDK for Python" readme = "README.md" license = "Apache-2.0" diff --git a/src/sap_cloud_sdk/core/telemetry/__init__.py b/src/sap_cloud_sdk/core/telemetry/__init__.py index 37c28f02..b1d616e4 100644 --- a/src/sap_cloud_sdk/core/telemetry/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/__init__.py @@ -37,6 +37,7 @@ ATTR_EXTENSION_ITEM_NAME, ATTR_EXTENSION_URL, ATTR_SOLUTION_ID, + ATTR_JOULE_STUDIO_GSID, ATTR_SUMMARY_TOTAL_OPERATION_COUNT, ATTR_SUMMARY_TOTAL_DURATION_MS, ATTR_SUMMARY_TOOL_CALL_COUNT, @@ -89,6 +90,7 @@ "ATTR_EXTENSION_ITEM_NAME", "ATTR_EXTENSION_URL", "ATTR_SOLUTION_ID", + "ATTR_JOULE_STUDIO_GSID", "ATTR_SUMMARY_TOTAL_OPERATION_COUNT", "ATTR_SUMMARY_TOTAL_DURATION_MS", "ATTR_SUMMARY_TOOL_CALL_COUNT", diff --git a/src/sap_cloud_sdk/core/telemetry/extensions.py b/src/sap_cloud_sdk/core/telemetry/extensions.py index b91efbaa..e07737d4 100644 --- a/src/sap_cloud_sdk/core/telemetry/extensions.py +++ b/src/sap_cloud_sdk/core/telemetry/extensions.py @@ -46,6 +46,7 @@ ATTR_EXTENSION_ITEM_NAME = "sap.extension.extension.item.name" ATTR_EXTENSION_URL = "sap.extension.extensionUrl" ATTR_SOLUTION_ID = "sap.extension.solution_id" +ATTR_JOULE_STUDIO_GSID = "sap.extension.joule_studio_gsid" class ExtensionType(str, Enum): @@ -72,6 +73,7 @@ def extension_context( item_name: str = "", extension_url: str = "", solution_id: str = "", + joule_studio_gsid: str = "", ) -> Generator[None, None, None]: """Set extension context in OTel baggage for propagation. @@ -90,6 +92,7 @@ def extension_context( - ``sap.extension.extension.item.name``: The tool or hook name - ``sap.extension.extensionUrl``: The extension URL (when provided) - ``sap.extension.solution_id``: The solution ID (when provided) + - ``sap.extension.joule_studio_gsid``: The global solution ID of Joule Studio (when provided) Args: capability_id: The capability ID for the extension @@ -101,7 +104,8 @@ def extension_context( extension_version: The version of the extension (e.g. ``"1"``). item_name: The name of the specific tool or hook being called. extension_url: The build extension URL (empty string if not available). - solution_id: The build solution ID (empty string if not available). + solution_id: The solution ID (empty string if not available). + joule_studio_gsid: The global solution ID of Joule Studio (empty string if not available). Yields: None. The context is active for the duration of the with block. @@ -121,6 +125,7 @@ def extension_context( extension_version="1", item_name="create_ticket", solution_id="my-solution-42", + joule_studio_gsid="gsid-value", ): result = await mcp_client.call_tool("create_ticket", args) ``` @@ -141,6 +146,10 @@ def extension_context( ctx = baggage.set_baggage(ATTR_EXTENSION_URL, extension_url, context=ctx) if solution_id: ctx = baggage.set_baggage(ATTR_SOLUTION_ID, solution_id, context=ctx) + if joule_studio_gsid: + ctx = baggage.set_baggage( + ATTR_JOULE_STUDIO_GSID, joule_studio_gsid, context=ctx + ) token = attach(ctx) try: @@ -168,6 +177,7 @@ def get_extension_context() -> dict[str, Any] | None: - ``item_name``: The tool or hook name - ``extension_url``: The extension URL (empty string if not set) - ``solution_id``: The solution ID (empty string if not set) + - ``joule_studio_gsid``: The global solution ID of Joule Studio (empty string if not set) Returns ``None`` if not in an extension context. @@ -194,6 +204,7 @@ def get_extension_context() -> dict[str, Any] | None: "item_name": baggage.get_baggage(ATTR_EXTENSION_ITEM_NAME), "extension_url": baggage.get_baggage(ATTR_EXTENSION_URL) or "", "solution_id": baggage.get_baggage(ATTR_SOLUTION_ID) or "", + "joule_studio_gsid": baggage.get_baggage(ATTR_JOULE_STUDIO_GSID) or "", } @@ -227,6 +238,7 @@ def get_extension_context() -> dict[str, Any] | None: (ATTR_EXTENSION_ITEM_NAME, "ext_item_name"), (ATTR_EXTENSION_URL, "ext_extension_url"), (ATTR_SOLUTION_ID, "ext_solution_id"), + (ATTR_JOULE_STUDIO_GSID, "ext_joule_studio_gsid"), ] @@ -239,14 +251,14 @@ def resolve_source_info( key: str, source_mapping: dict[str, Any] | None, fallback_name: str, -) -> tuple[str, str, str, str, str]: - """Resolve extension name, id, version, url, and solution_id from a source mapping. +) -> tuple[str, str, str, str, str, str]: + """Resolve extension name, id, version, url, solution_id, and joule_studio_gsid from a source mapping. Source mapping values may be ``ExtensionSourceInfo`` dataclass instances (with attributes ``extension_name``, ``extension_id``, - ``extension_version``, ``extension_url``, ``solution_id``) or plain dicts + ``extension_version``, ``extension_url``, ``solution_id``, ``joule_studio_gsid``) or plain dicts with camelCase keys (``extensionName``, ``extensionId``, - ``extensionVersion``, ``extensionUrl``, ``solutionId``). + ``extensionVersion``, ``extensionUrl``, ``solutionId``, ``jouleStudioGsid``). Falls back to *fallback_name* for the name and empty strings for other fields when the key is not found in the mapping. @@ -260,11 +272,11 @@ def resolve_source_info( name is empty. Returns: - Tuple of ``(extension_name, extension_id, extension_version, extension_url, solution_id)``. + Tuple of ``(extension_name, extension_id, extension_version, extension_url, solution_id, joule_studio_gsid)``. """ info = (source_mapping or {}).get(key) if info is None: - return (fallback_name or "unknown", "", "", "", "") + return (fallback_name or "unknown", "", "", "", "", "") # SDK ExtensionSourceInfo dataclass (duck-typed to avoid circular import) if hasattr(info, "extension_name"): return ( @@ -273,6 +285,7 @@ def resolve_source_info( str(info.extension_version) if info.extension_version else "", getattr(info, "extension_url", "") or "", getattr(info, "solution_id", "") or "", + getattr(info, "joule_studio_gsid", "") or "", ) # Plain dict with camelCase keys (older SDK or manual construction) if isinstance(info, dict): @@ -282,8 +295,9 @@ def resolve_source_info( str(info.get("extensionVersion", "")) or "", info.get("extensionUrl") or "", info.get("solutionId") or "", + info.get("jouleStudioGsid") or "", ) - return (fallback_name or "unknown", "", "", "", "") + return (fallback_name or "unknown", "", "", "", "", "") # --------------------------------------------------------------------------- @@ -300,6 +314,7 @@ def build_extension_span_attributes( item_name: str, extension_url: str = "", solution_id: str = "", + joule_studio_gsid: str = "", ) -> dict[str, Any]: """Build the full set of ``sap.extension.*`` span attributes. @@ -311,7 +326,8 @@ def build_extension_span_attributes( capability: Extension capability ID (e.g. ``"default"``). item_name: Name of the specific tool or hook being called. extension_url: Build extension URL (empty string if not available). - solution_id: Build solution ID (empty string if not available). + solution_id: Solution ID (empty string if not available). + joule_studio_gsid: Global solution ID (empty string if not available). Returns: Dict with all ``sap.extension.*`` attribute keys. @@ -329,6 +345,8 @@ def build_extension_span_attributes( attrs[ATTR_EXTENSION_URL] = extension_url if solution_id: attrs[ATTR_SOLUTION_ID] = solution_id + if joule_studio_gsid: + attrs[ATTR_JOULE_STUDIO_GSID] = joule_studio_gsid return attrs @@ -447,9 +465,14 @@ async def call_extension_tool( See Also: :func:`call_extension_hook` for hook-based extensions. """ - resolved_name, resolved_id, resolved_version, resolved_url, resolved_solution_id = ( - resolve_source_info(tool_name, source_mapping, "unknown") - ) + ( + resolved_name, + resolved_id, + resolved_version, + resolved_url, + resolved_solution_id, + resolved_joule_studio_gsid, + ) = resolve_source_info(tool_name, source_mapping, "unknown") attrs = build_extension_span_attributes( resolved_name, @@ -460,6 +483,7 @@ async def call_extension_tool( tool_name, extension_url=resolved_url, solution_id=resolved_solution_id, + joule_studio_gsid=resolved_joule_studio_gsid, ) t0 = time.monotonic() @@ -474,6 +498,7 @@ async def call_extension_tool( item_name=tool_name, extension_url=resolved_url, solution_id=resolved_solution_id, + joule_studio_gsid=resolved_joule_studio_gsid, ), _tracer.start_as_current_span( f"extension_tool {tool_name}", @@ -526,9 +551,14 @@ async def call_extension_hook( Returns: The hook's response. """ - resolved_name, resolved_id, resolved_version, resolved_url, resolved_solution_id = ( - resolve_source_info(hook_id, source_mapping, extension_name) - ) + ( + resolved_name, + resolved_id, + resolved_version, + resolved_url, + resolved_solution_id, + resolved_joule_studio_gsid, + ) = resolve_source_info(hook_id, source_mapping, extension_name) item_name = getattr(hook, "name", None) or hook_id @@ -541,6 +571,7 @@ async def call_extension_hook( item_name, extension_url=resolved_url, solution_id=resolved_solution_id, + joule_studio_gsid=resolved_joule_studio_gsid, ) t0 = time.monotonic() @@ -555,6 +586,7 @@ async def call_extension_hook( item_name=item_name, extension_url=resolved_url, solution_id=resolved_solution_id, + joule_studio_gsid=resolved_joule_studio_gsid, ), _tracer.start_as_current_span( f"extension_hook {item_name}", diff --git a/src/sap_cloud_sdk/extensibility/_models.py b/src/sap_cloud_sdk/extensibility/_models.py index 0a5eab3a..b3c73a48 100644 --- a/src/sap_cloud_sdk/extensibility/_models.py +++ b/src/sap_cloud_sdk/extensibility/_models.py @@ -411,6 +411,8 @@ class ExtensionSourceInfo: extension_url: Build extension URL, or empty string if not provided. solution_id: Build solution ID extracted from extension_url, or empty string if not available. + joule_studio_gsid: Global solution ID of Joule Studio, or empty string + if not available. """ extension_name: str @@ -418,6 +420,7 @@ class ExtensionSourceInfo: extension_id: str extension_url: str = "" solution_id: str = "" + joule_studio_gsid: str = "" @classmethod def from_dict(cls, obj: Dict[str, Any]) -> ExtensionSourceInfo: @@ -430,7 +433,8 @@ def from_dict(cls, obj: Dict[str, Any]) -> ExtensionSourceInfo: "extensionVersion": "1", "extensionId": "a1b2c3d4-...", "extensionUrl": "https://...", - "solutionId": "f9cbd5c1-..." + "solutionId": "f9cbd5c1-...", + "jouleStudioGsid": "gsid-value-..." } Args: @@ -445,6 +449,7 @@ def from_dict(cls, obj: Dict[str, Any]) -> ExtensionSourceInfo: extension_id=obj.get("extensionId", ""), extension_url=obj.get("extensionUrl") or "", solution_id=obj.get("solutionId") or "", + joule_studio_gsid=obj.get("jouleStudioGsid") or "", ) @classmethod @@ -469,6 +474,7 @@ def from_value(cls, value: Any) -> ExtensionSourceInfo: extension_id="", extension_url="", solution_id="", + joule_studio_gsid="", ) if isinstance(value, dict): return cls.from_dict(value) @@ -478,6 +484,7 @@ def from_value(cls, value: Any) -> ExtensionSourceInfo: extension_id="", extension_url="", solution_id="", + joule_studio_gsid="", ) diff --git a/src/sap_cloud_sdk/extensibility/_ums_transport.py b/src/sap_cloud_sdk/extensibility/_ums_transport.py index 82e8d2cd..896feead 100644 --- a/src/sap_cloud_sdk/extensibility/_ums_transport.py +++ b/src/sap_cloud_sdk/extensibility/_ums_transport.py @@ -66,6 +66,7 @@ title extensionVersion solutionId + jouleStudioGsid capabilityImplementations { capabilityId instruction { text } @@ -327,6 +328,7 @@ def _build_source_mapping( extension_version=node.get("extensionVersion", ""), extension_id=node.get("id", ""), solution_id=node.get("solutionId") or "", + joule_studio_gsid=node.get("jouleStudioGsid") or "", ) for cap_impl in node.get("capabilityImplementations", []): diff --git a/tests/core/unit/telemetry/test_extensions.py b/tests/core/unit/telemetry/test_extensions.py index de6b1619..90607c53 100644 --- a/tests/core/unit/telemetry/test_extensions.py +++ b/tests/core/unit/telemetry/test_extensions.py @@ -19,6 +19,7 @@ ATTR_EXTENSION_ITEM_NAME, ATTR_EXTENSION_URL, ATTR_SOLUTION_ID, + ATTR_JOULE_STUDIO_GSID, ATTR_SUMMARY_TOTAL_OPERATION_COUNT, ATTR_SUMMARY_TOTAL_DURATION_MS, ATTR_SUMMARY_TOOL_CALL_COUNT, @@ -100,7 +101,7 @@ class TestExtensionContext: """Test suite for extension_context function.""" def test_extension_context_sets_all_baggage(self): - """Test extension_context sets all baggage values including url and solution_id.""" + """Test extension_context sets all baggage values including url, solution_id, and joule_studio_gsid.""" captured_baggage = {} def mock_set_baggage(key, value, context=None): @@ -128,6 +129,7 @@ def mock_set_baggage(key, value, context=None): item_name="create_ticket", extension_url="https://ext.example.com", solution_id="sol-789", + joule_studio_gsid="gsid-test-123", ): pass @@ -140,6 +142,7 @@ def mock_set_baggage(key, value, context=None): assert captured_baggage[ATTR_EXTENSION_ITEM_NAME] == "create_ticket" assert captured_baggage[ATTR_EXTENSION_URL] == "https://ext.example.com" assert captured_baggage[ATTR_SOLUTION_ID] == "sol-789" + assert captured_baggage[ATTR_JOULE_STUDIO_GSID] == "gsid-test-123" def test_extension_context_defaults_for_new_params(self): """Test extension_context uses defaults when new params are omitted.""" @@ -177,6 +180,7 @@ def mock_set_baggage(key, value, context=None): assert captured_baggage[ATTR_EXTENSION_ITEM_NAME] == "" assert ATTR_EXTENSION_URL not in captured_baggage assert ATTR_SOLUTION_ID not in captured_baggage + assert ATTR_JOULE_STUDIO_GSID not in captured_baggage def test_extension_context_hook_type(self): """Test extension_context with hook extension type.""" @@ -434,6 +438,7 @@ def mock_get_baggage(key): ATTR_EXTENSION_ITEM_NAME: "create_ticket", ATTR_EXTENSION_URL: "https://ext.example.com", ATTR_SOLUTION_ID: "sol-789", + ATTR_JOULE_STUDIO_GSID: "gsid-789", } return values.get(key) @@ -451,6 +456,7 @@ def mock_get_baggage(key): assert result["item_name"] == "create_ticket" assert result["extension_url"] == "https://ext.example.com" assert result["solution_id"] == "sol-789" + assert result["joule_studio_gsid"] == "gsid-789" def test_with_different_extension_types(self): """Test get_extension_context with different extension types.""" @@ -565,6 +571,7 @@ def test_extension_context_and_get_work_together(self): item_name="create_ticket", extension_url="https://ext.example.com", solution_id="sol-789", + joule_studio_gsid="gsid-789", ): result_during = get_extension_context() assert result_during is not None @@ -577,6 +584,7 @@ def test_extension_context_and_get_work_together(self): assert result_during["item_name"] == "create_ticket" assert result_during["extension_url"] == "https://ext.example.com" assert result_during["solution_id"] == "sol-789" + assert result_during["joule_studio_gsid"] == "gsid-789" result_after = get_extension_context() assert result_after is None @@ -640,6 +648,7 @@ def test_extension_context_defaults_integration(self): assert result["item_name"] == "" assert result["extension_url"] == "" assert result["solution_id"] == "" + assert result["joule_studio_gsid"] == "" # --------------------------------------------------------------------------- @@ -651,7 +660,7 @@ class TestResolveSourceInfo: """Tests for resolve_source_info.""" def test_none_mapping_returns_fallback(self): - name, ext_id, ver, url, sid = resolve_source_info("key", None, "fallback") + name, ext_id, ver, url, sid, _ = resolve_source_info("key", None, "fallback") assert name == "fallback" assert ext_id == "" assert ver == "" @@ -659,13 +668,13 @@ def test_none_mapping_returns_fallback(self): assert sid == "" def test_missing_key_returns_fallback(self): - name, ext_id, ver, url, sid = resolve_source_info( + name, ext_id, ver, url, sid, _ = resolve_source_info( "missing", {"other": {}}, "fb" ) assert name == "fb" def test_empty_fallback_returns_unknown(self): - name, _, _, _, _ = resolve_source_info("missing", None, "") + name, _, _, _, _, _ = resolve_source_info("missing", None, "") assert name == "unknown" def test_dataclass_source_info(self): @@ -676,7 +685,7 @@ class FakeSourceInfo: extension_version: str info = FakeSourceInfo("My Ext", "uuid-1", "3") - name, ext_id, ver, url, sid = resolve_source_info("k", {"k": info}, "fb") + name, ext_id, ver, url, sid, _ = resolve_source_info("k", {"k": info}, "fb") assert name == "My Ext" assert ext_id == "uuid-1" assert ver == "3" @@ -691,7 +700,7 @@ class FakeSourceInfo: extension_version: str info = FakeSourceInfo("", "uuid-1", "3") - name, _, _, _, _ = resolve_source_info("k", {"k": info}, "fb") + name, _, _, _, _, _ = resolve_source_info("k", {"k": info}, "fb") assert name == "fb" def test_dict_source_info(self): @@ -700,7 +709,7 @@ def test_dict_source_info(self): "extensionId": "uuid-2", "extensionVersion": "5", } - name, ext_id, ver, url, sid = resolve_source_info("k", {"k": info}, "fb") + name, ext_id, ver, url, sid, _ = resolve_source_info("k", {"k": info}, "fb") assert name == "Dict Ext" assert ext_id == "uuid-2" assert ver == "5" @@ -709,11 +718,11 @@ def test_dict_source_info(self): def test_dict_empty_name_uses_fallback(self): info = {"extensionName": "", "extensionId": "x", "extensionVersion": "1"} - name, _, _, _, _ = resolve_source_info("k", {"k": info}, "fb") + name, _, _, _, _, _ = resolve_source_info("k", {"k": info}, "fb") assert name == "fb" def test_unknown_type_returns_fallback(self): - name, ext_id, ver, url, sid = resolve_source_info("k", {"k": 42}, "fb") + name, ext_id, ver, url, sid, _ = resolve_source_info("k", {"k": 42}, "fb") assert name == "fb" assert ext_id == "" assert ver == "" @@ -728,7 +737,7 @@ class FakeSourceInfo: extension_version: str | None info = FakeSourceInfo("Ext", "id", None) - _, _, ver, _, _ = resolve_source_info("k", {"k": info}, "fb") + _, _, ver, _, _, _ = resolve_source_info("k", {"k": info}, "fb") assert ver == "" def test_dataclass_with_url_and_solution_id(self): @@ -741,7 +750,7 @@ class FakeSourceInfo: solution_id: str info = FakeSourceInfo("Ext", "id", "1", "https://url", "sol-123") - _, _, _, url, sid = resolve_source_info("k", {"k": info}, "fb") + _, _, _, url, sid, _ = resolve_source_info("k", {"k": info}, "fb") assert url == "https://url" assert sid == "sol-123" @@ -753,7 +762,7 @@ def test_dict_with_url_and_solution_id(self): "extensionUrl": "https://url", "solutionId": "sol-456", } - _, _, _, url, sid = resolve_source_info("k", {"k": info}, "fb") + _, _, _, url, sid, _ = resolve_source_info("k", {"k": info}, "fb") assert url == "https://url" assert sid == "sol-456" @@ -794,9 +803,11 @@ def test_includes_solution_id_when_provided(self): "tool", extension_url="https://url", solution_id="sol-123", + joule_studio_gsid="gsid-123", ) assert attrs["sap.extension.solution_id"] == "sol-123" assert attrs["sap.extension.extensionUrl"] == "https://url" + assert attrs["sap.extension.joule_studio_gsid"] == "gsid-123" def test_omits_solution_id_when_empty(self): attrs = build_extension_span_attributes( @@ -809,6 +820,7 @@ def test_omits_solution_id_when_empty(self): ) assert "sap.extension.solution_id" not in attrs assert "sap.extension.extensionUrl" not in attrs + assert "sap.extension.joule_studio_gsid" not in attrs # --------------------------------------------------------------------------- @@ -1084,6 +1096,7 @@ def test_adds_attributes_in_extension_context(self): item_name="tool1", extension_url="https://ext.example.com", solution_id="sol-42", + joule_studio_gsid="gsid-42", ): result = filt.filter(record) @@ -1097,6 +1110,7 @@ def test_adds_attributes_in_extension_context(self): assert getattr(record, "ext_item_name") == "tool1" assert getattr(record, "ext_extension_url") == "https://ext.example.com" assert getattr(record, "ext_solution_id") == "sol-42" + assert getattr(record, "ext_joule_studio_gsid") == "gsid-42" def test_no_attributes_outside_context(self): filt = ExtensionContextLogFilter() @@ -1132,3 +1146,4 @@ def test_empty_values_set_as_empty_string(self): assert getattr(record, "ext_item_name") == "" assert getattr(record, "ext_extension_url") == "" assert getattr(record, "ext_solution_id") == "" + assert getattr(record, "ext_joule_studio_gsid") == "" diff --git a/uv.lock b/uv.lock index 8a116925..736e0817 100644 --- a/uv.lock +++ b/uv.lock @@ -1013,9 +1013,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" }, @@ -1023,9 +1021,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" }, @@ -1033,9 +1029,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" }, @@ -1043,9 +1037,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" }, @@ -1053,18 +1045,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" }, @@ -1072,9 +1060,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" }, @@ -3939,7 +3925,7 @@ wheels = [ [[package]] name = "sap-cloud-sdk" -version = "0.47.0" +version = "0.49.1" source = { editable = "." } dependencies = [ { name = "cryptography" },