From 45c36e558df98b7516919430fb3c9d0b1b68a259 Mon Sep 17 00:00:00 2001 From: speakeasybot Date: Sat, 27 Jun 2026 03:48:52 +0000 Subject: [PATCH 1/2] ci: regenerated with OpenAPI Doc , Speakeasy CLI 1.781.0 --- .speakeasy/gen.lock | 85 ++++--- .speakeasy/gen.yaml | 12 +- .speakeasy/out.openapi.yaml | 2 + .speakeasy/workflow.lock | 14 +- RELEASES.md | 12 +- pyproject.toml | 2 +- src/cribl_mgmt_plane/_version.py | 6 +- src/cribl_mgmt_plane/apicredentials.py | 18 +- src/cribl_mgmt_plane/types/__init__.py | 3 + src/cribl_mgmt_plane/types/base64fileinput.py | 43 ++++ src/cribl_mgmt_plane/utils/eventstreaming.py | 215 ++++++++++-------- src/cribl_mgmt_plane/utils/forms.py | 5 + src/cribl_mgmt_plane/utils/requestbodies.py | 1 + src/cribl_mgmt_plane/utils/retries.py | 23 ++ src/cribl_mgmt_plane/utils/serializers.py | 87 ++++++- src/cribl_mgmt_plane/workspaces.py | 18 +- 16 files changed, 366 insertions(+), 180 deletions(-) create mode 100644 src/cribl_mgmt_plane/types/base64fileinput.py diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index dda7666..5b7e471 100644 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -1,40 +1,40 @@ lockVersion: 2.0.0 id: 897b2045-55e2-49ca-a37b-2daf10d4e6ea management: - docChecksum: ff02bf1e8c144fe95e7799aea0b1be3c + docChecksum: cc67ab449e92722187b0df80458d268a docVersion: "1.0" - speakeasyVersion: 1.761.9 - generationVersion: 2.881.4 - releaseVersion: 0.3.1 - configChecksum: 0ffe3a6fad91754af4cab7bb8a6be1e6 + speakeasyVersion: 1.781.0 + generationVersion: 2.907.0 + releaseVersion: 0.4.0 + configChecksum: 6613ddd73135b1cd3e2993db17993149 repoURL: https://github.com/criblio/cribl_cloud_management_sdk_python.git installationURL: https://github.com/criblio/cribl_cloud_management_sdk_python.git published: true persistentEdits: - generation_id: 861964fc-6c49-439c-8bc5-2e25bc9ab6c7 - pristine_commit_hash: f33925ba856139d650650c3aa9034bd7341d2d86 - pristine_tree_hash: 9dc6307eb8659c35a2b556911442236262efc8bb + generation_id: 1f6137f3-d732-4791-8aff-f7c4140c50ed + pristine_commit_hash: efe410f1071c1860fa25985926a8b7df84162832 + pristine_tree_hash: ef75a476fc86361e1796379cde79a82169fb0bf6 features: python: - additionalDependencies: 1.0.0 - core: 6.0.21 + additionalDependencies: 1.1.0 + core: 6.0.30 defaultEnabledRetries: 0.2.0 devContainers: 3.0.0 enumUnions: 0.1.1 - envVarSecurityUsage: 0.3.2 + envVarSecurityUsage: 0.3.3 flatRequests: 1.0.1 flattening: 3.1.1 globalSecurity: 3.0.7 globalSecurityCallbacks: 1.0.0 globalServerURLs: 3.2.1 groups: 3.0.1 - methodArguments: 1.0.2 + methodArguments: 1.1.1 nameOverrides: 3.0.3 oauth2ClientCredentials: 2.1.5 openEnums: 1.0.4 responseFormat: 1.1.0 - retries: 3.0.5 - sdkHooks: 1.2.1 + retries: 3.0.6 + sdkHooks: 1.2.2 trackedFiles: .devcontainer/README.md: id: b170c0f184ac @@ -250,8 +250,8 @@ trackedFiles: pristine_git_object: fde5e0418e98324b2b3b2a8e02559e575b2f6cfa pyproject.toml: id: 5d07e7d72637 - last_write_checksum: sha1:599078c7d085e65429e5684022f52a776e59a681 - pristine_git_object: af28195bd6f2f6132a0741eb9abb7c2dfd8a0f0d + last_write_checksum: sha1:ce88e79d6477b62b6b855d2084f582e37d15ddd2 + pristine_git_object: ab9ba401ab5745e007bce59f86825b538a7d662d scripts/prepare_readme.py: id: e0c5957a6035 last_write_checksum: sha1:24bfcb3bc609310d0b86cb75ca844b42d4c9c248 @@ -282,12 +282,12 @@ trackedFiles: pristine_git_object: 8660a4d81ec54cbc43bcb4ed058985af8dcc715b src/cribl_mgmt_plane/_version.py: id: 8ec199fcd31a - last_write_checksum: sha1:92cf720c84e586fbaeebbc38cb72ebbd072fa0a2 - pristine_git_object: 54f4bc2a4a0afba23172a74719c8efe99fe997ae + last_write_checksum: sha1:85454e8c690ccdf7ef88b0773c7875b9c590c49a + pristine_git_object: 99c2167bdb2a48053e1fa29b65371b13536b5c37 src/cribl_mgmt_plane/apicredentials.py: id: d344dfa0de1f - last_write_checksum: sha1:c0a16485bb38d472aa6972c79a3d6a88efd10861 - pristine_git_object: c97b1b181de890ca0fe9b9cf557ba984ed95ffc7 + last_write_checksum: sha1:69a683b137ef782c020a9dd50953ee70b1d9f854 + pristine_git_object: d6b343d9197dd3f9a587295ff51aeacf72a03627 src/cribl_mgmt_plane/basesdk.py: id: 024a102b240a last_write_checksum: sha1:81612e29f97ca18085e9c029df956289aabbad29 @@ -446,8 +446,12 @@ trackedFiles: pristine_git_object: 65c499c66e905195a345a02c21a0050685df6926 src/cribl_mgmt_plane/types/__init__.py: id: ac748e42a9e7 - last_write_checksum: sha1:140ebdd01a46f92ffc710c52c958c4eba3cf68ed - pristine_git_object: fc76fe0c5505e29859b5d2bb707d48fd27661b8c + last_write_checksum: sha1:f9ad14217f832e74f594285960125add50324be9 + pristine_git_object: faa268137bc01c9d08cfadc4797017db48747a96 + src/cribl_mgmt_plane/types/base64fileinput.py: + id: 5a713441aa6e + last_write_checksum: sha1:1522687ae3398374c35710cad993a6e82b5ab99d + pristine_git_object: 862566fe2b1db830276b390e136e65090e5963d2 src/cribl_mgmt_plane/types/basemodel.py: id: 61728c103c49 last_write_checksum: sha1:10d84aedeb9d35edfdadf2c3020caa1d24d8b584 @@ -474,12 +478,12 @@ trackedFiles: pristine_git_object: 3324e1bc2668c54c4d5f5a1a845675319757a828 src/cribl_mgmt_plane/utils/eventstreaming.py: id: 30f167e350de - last_write_checksum: sha1:620d78a8b4e3b854e08d136e02e40a01a786bd70 - pristine_git_object: 3bdcd6d3d4fc772cb7f5fca8685dcdc8c85e13e8 + last_write_checksum: sha1:7d1dc68f8b48486ab646653aa05cc38752e1f912 + pristine_git_object: a8d4fe5cc88d3c7337339e1b36a61bbf7ca8c4eb src/cribl_mgmt_plane/utils/forms.py: id: 89247def37c3 - last_write_checksum: sha1:15fa7e9ab1611e062a9984cf06cb20969713d295 - pristine_git_object: f961e76beaf0a8b1fe0dda44754a74eebd3608e7 + last_write_checksum: sha1:a971cdb120ad3d416d296d5d0ad89e4808350a7f + pristine_git_object: fdf0dc9b2a67bca773eefe6b471498cccaa83424 src/cribl_mgmt_plane/utils/headers.py: id: 97c0f4ea3c60 last_write_checksum: sha1:7c6df233ee006332b566a8afa9ce9a245941d935 @@ -498,20 +502,20 @@ trackedFiles: pristine_git_object: c04e0db82b68eca041f2cb2614d748fbac80fd41 src/cribl_mgmt_plane/utils/requestbodies.py: id: 91737dcb4d38 - last_write_checksum: sha1:41e2d2d2d3ecc394c8122ca4d4b85e1c3e03f054 - pristine_git_object: 1de32b6d26f46590232f398fdba6ce0072f1659c + last_write_checksum: sha1:e1fef575283b7fe7fe2ad392dbbb3fb105309124 + pristine_git_object: 591415af8e64baa410627b507d2740afb5387d13 src/cribl_mgmt_plane/utils/retries.py: id: e0e855f01c10 - last_write_checksum: sha1:471372f5c5d1dd5583239c9cf3c75f1b636e5d87 - pristine_git_object: af07d4e941007af4213c5ec9047ef8a2fca04e5e + last_write_checksum: sha1:9fcb404e0a5113634df964b4f393ba4eea5a76f3 + pristine_git_object: bab2066f128973f7a38a94ccc5b378bd849a3cd9 src/cribl_mgmt_plane/utils/security.py: id: 6e9559bdd145 last_write_checksum: sha1:b978112e0dd964d585f424d151228d1d18995ba0 pristine_git_object: 1aa12865633519ab01b57dd216726fadb56825e2 src/cribl_mgmt_plane/utils/serializers.py: id: 8199ce0531cd - last_write_checksum: sha1:61009f2e4ef6613a1a5af813fe020373dae5a492 - pristine_git_object: d2149f8b909cb96628db140ac3cddb1b1e981367 + last_write_checksum: sha1:7485f1425b0661fd84836186570df90207eec6af + pristine_git_object: 1031ed930bad5ece220cf7416a56c29f40f0588b src/cribl_mgmt_plane/utils/unmarshal_json_response.py: id: 3441b981eaea last_write_checksum: sha1:2e70698f9b2355b1a0ff412353333b746d94edd5 @@ -526,8 +530,8 @@ trackedFiles: pristine_git_object: dae01a44384ac3bc13ae07453a053bf6c898ebe3 src/cribl_mgmt_plane/workspaces.py: id: bd7d8da788fc - last_write_checksum: sha1:08a5718813abae69789d2193be6aa4ba30e0435f - pristine_git_object: 4d5efe187cffbab33b37271a6280886f03b8da1e + last_write_checksum: sha1:cb68270b2d2591de26af473314b787f51882c634 + pristine_git_object: 68c5bf8775acbb657a42006933a7250e33ff0a81 examples: dummyServiceStatus: {} getHealthStatus: @@ -650,19 +654,6 @@ examples: examplesVersion: 1.0.2 generatedTests: dummyServiceStatus: "2025-07-02T19:49:08+02:00" -releaseNotes: | - ## Python SDK Changes - - This version of the SDK is generated for Cribl.Cloud. - - ### API Credentials — IP allowlist - - Optional CIDR-based IP restrictions on API credentials: set on create and update; returned on list and get. - - * `cribl_mgmt_plane.api_credentials.list()` — `response.items[].ip_allowlist` - * `cribl_mgmt_plane.api_credentials.create()` — `request.ip_allowlist`; `response.ip_allowlist` - * `cribl_mgmt_plane.api_credentials.update()` — `request.ip_allowlist` - * `cribl_mgmt_plane.api_credentials.get()` — `response.ip_allowlist` generatedFiles: - .devcontainer/README.md - .devcontainer/devcontainer.json diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index 15cfa20..b96c090 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -33,7 +33,7 @@ generation: generateNewTests: false skipResponseBodyAssertions: false python: - version: 0.3.1 + version: 0.4.0 additionalDependencies: dev: {} main: {} @@ -44,6 +44,7 @@ python: authors: - Speakeasy baseErrorName: CriblMgmtPlaneError + bodyVariantOverloads: false clientServerStatusCodesAsErrors: true constFieldCasing: upper defaultErrorName: APIError @@ -51,6 +52,10 @@ python: enableCustomCodeRegions: false enumFormat: enum envVarPrefix: CRIBLMGMTPLANE + errorSchemaValidation: true + eventStreamClassNames: + async: EventStreamAsync + sync: EventStream fixFlags: asyncPaginationSep2025: true conflictResistantModelImportsFeb2026: true @@ -70,18 +75,23 @@ python: webhooks: "" inferUnionDiscriminators: true inputModelSuffix: input + inputTypedDictSuffix: TypedDict legacyPyright: true license: "" maxMethodParams: 999 methodArguments: infer-optional-args + methodTimeoutArgument: timeout-ms + methodTimeoutUnits: milliseconds moduleName: "" multipartArrayFormat: legacy + optionalDependencies: {} outputModelSuffix: output packageManager: poetry packageName: cribl-mgmt-plane preApplyUnionDiscriminators: false pytestFilterWarnings: [] pytestTimeout: 0 + rawResponseHelpers: false responseFormat: flat sseFlatResponse: false templateVersion: v2 diff --git a/.speakeasy/out.openapi.yaml b/.speakeasy/out.openapi.yaml index 66a7c61..684edff 100644 --- a/.speakeasy/out.openapi.yaml +++ b/.speakeasy/out.openapi.yaml @@ -387,6 +387,8 @@ info: tags: - name: apiCredentials description: Operations related to API credentials + - name: billing + description: Operations related to Billing and FinOps data - name: workspaces description: Operations related to Workspaces - name: health diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 1fd5e9c..e6027fb 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,9 +1,9 @@ -speakeasyVersion: 1.761.9 +speakeasyVersion: 1.781.0 sources: Cribl Cloud Management API: sourceNamespace: cribl-cloud-management-api - sourceRevisionDigest: sha256:c77143c6cb9382b2267881145e8d9d5fb9cbf56d913cefe254f03dbc74c7268b - sourceBlobDigest: sha256:32512a0b7eb4acffcb144a9e07fd8a594085173d114e19a807308a643222a105 + sourceRevisionDigest: sha256:21497309fe3f8463b9b185a8a7f377b473591c60cdbb3dfb942154dc01e473cb + sourceBlobDigest: sha256:42cd7b87ad65230789e911348371981b059f71cd86c91e9ef1cf258be5ae356c tags: - latest - "1.0" @@ -11,13 +11,13 @@ targets: cribl-mgmt-plane: source: Cribl Cloud Management API sourceNamespace: cribl-cloud-management-api - sourceRevisionDigest: sha256:c77143c6cb9382b2267881145e8d9d5fb9cbf56d913cefe254f03dbc74c7268b - sourceBlobDigest: sha256:32512a0b7eb4acffcb144a9e07fd8a594085173d114e19a807308a643222a105 + sourceRevisionDigest: sha256:21497309fe3f8463b9b185a8a7f377b473591c60cdbb3dfb942154dc01e473cb + sourceBlobDigest: sha256:42cd7b87ad65230789e911348371981b059f71cd86c91e9ef1cf258be5ae356c codeSamplesNamespace: cribl-cloud-management-api-python-code-samples - codeSamplesRevisionDigest: sha256:6b0292034845909d3f5d909538c14250410c28d4972c282ddc9e1b869a75fe24 + codeSamplesRevisionDigest: sha256:b86158ab3e371d7c6601b13055466f2ac8e4a17efed3e7d4bec34d839bf6af1a workflow: workflowVersion: 1.0.0 - speakeasyVersion: 1.761.9 + speakeasyVersion: 1.781.0 sources: Cribl Cloud Management API: inputs: diff --git a/RELEASES.md b/RELEASES.md index 3ecbaa6..742167b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -358,4 +358,14 @@ Based on: ### Generated - [python v0.3.1] . ### Releases -- [PyPI v0.3.1] https://pypi.org/project/cribl-mgmt-plane/0.3.1 - . \ No newline at end of file +- [PyPI v0.3.1] https://pypi.org/project/cribl-mgmt-plane/0.3.1 - . + +## 2026-06-27 03:47:42 +### Changes +Based on: +- OpenAPI Doc +- Speakeasy CLI 1.781.0 (2.907.0) https://github.com/speakeasy-api/speakeasy +### Generated +- [python v0.4.0] . +### Releases +- [PyPI v0.4.0] https://pypi.org/project/cribl-mgmt-plane/0.4.0 - . \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index af28195..ab9ba40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "cribl-mgmt-plane" -version = "0.3.1" +version = "0.4.0" description = "Python Client SDK Generated by Speakeasy." authors = [{ name = "Speakeasy" },] readme = "README-PYPI.md" diff --git a/src/cribl_mgmt_plane/_version.py b/src/cribl_mgmt_plane/_version.py index 54f4bc2..99c2167 100644 --- a/src/cribl_mgmt_plane/_version.py +++ b/src/cribl_mgmt_plane/_version.py @@ -3,10 +3,10 @@ import importlib.metadata __title__: str = "cribl-mgmt-plane" -__version__: str = "0.3.1" +__version__: str = "0.4.0" __openapi_doc_version__: str = "1.0" -__gen_version__: str = "2.881.4" -__user_agent__: str = "speakeasy-sdk/python 0.3.1 2.881.4 1.0 cribl-mgmt-plane" +__gen_version__: str = "2.907.0" +__user_agent__: str = "speakeasy-sdk/python 0.4.0 2.907.0 1.0 cribl-mgmt-plane" try: if __package__ is not None: diff --git a/src/cribl_mgmt_plane/apicredentials.py b/src/cribl_mgmt_plane/apicredentials.py index c97b1b1..d6b343d 100644 --- a/src/cribl_mgmt_plane/apicredentials.py +++ b/src/cribl_mgmt_plane/apicredentials.py @@ -6,7 +6,7 @@ from cribl_mgmt_plane.types import OptionalNullable, UNSET from cribl_mgmt_plane.utils import get_security_from_env from cribl_mgmt_plane.utils.unmarshal_json_response import unmarshal_json_response -from typing import Any, List, Mapping, Optional, Union +from typing import Any, Iterable, List, Mapping, Optional, Union class APICredentials(BaseSDK): @@ -206,7 +206,7 @@ def create( roles: Union[ models.APICredentialRolesSchema, models.APICredentialRolesSchemaTypedDict ], - ip_allowlist: Optional[List[str]] = None, + ip_allowlist: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -244,7 +244,7 @@ def create( description=description, enabled=enabled, roles=utils.get_pydantic_model(roles, models.APICredentialRolesSchema), - ip_allowlist=ip_allowlist, + ip_allowlist=utils.unmarshal(ip_allowlist, Optional[List[str]]), ), ) @@ -330,7 +330,7 @@ async def create_async( roles: Union[ models.APICredentialRolesSchema, models.APICredentialRolesSchemaTypedDict ], - ip_allowlist: Optional[List[str]] = None, + ip_allowlist: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -368,7 +368,7 @@ async def create_async( description=description, enabled=enabled, roles=utils.get_pydantic_model(roles, models.APICredentialRolesSchema), - ip_allowlist=ip_allowlist, + ip_allowlist=utils.unmarshal(ip_allowlist, Optional[List[str]]), ), ) @@ -458,7 +458,7 @@ def update( models.APICredentialRolesSchemaTypedDict, ] ] = None, - ip_allowlist: Optional[List[str]] = None, + ip_allowlist: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -500,7 +500,7 @@ def update( roles=utils.get_pydantic_model( roles, Optional[models.APICredentialRolesSchema] ), - ip_allowlist=ip_allowlist, + ip_allowlist=utils.unmarshal(ip_allowlist, Optional[List[str]]), ), ) @@ -582,7 +582,7 @@ async def update_async( models.APICredentialRolesSchemaTypedDict, ] ] = None, - ip_allowlist: Optional[List[str]] = None, + ip_allowlist: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -624,7 +624,7 @@ async def update_async( roles=utils.get_pydantic_model( roles, Optional[models.APICredentialRolesSchema] ), - ip_allowlist=ip_allowlist, + ip_allowlist=utils.unmarshal(ip_allowlist, Optional[List[str]]), ), ) diff --git a/src/cribl_mgmt_plane/types/__init__.py b/src/cribl_mgmt_plane/types/__init__.py index fc76fe0..faa2681 100644 --- a/src/cribl_mgmt_plane/types/__init__.py +++ b/src/cribl_mgmt_plane/types/__init__.py @@ -1,5 +1,6 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" +from .base64fileinput import Base64EncodedString, Base64FileInput from .basemodel import ( BaseModel, Nullable, @@ -11,6 +12,8 @@ ) __all__ = [ + "Base64EncodedString", + "Base64FileInput", "BaseModel", "Nullable", "OptionalNullable", diff --git a/src/cribl_mgmt_plane/types/base64fileinput.py b/src/cribl_mgmt_plane/types/base64fileinput.py new file mode 100644 index 0000000..862566f --- /dev/null +++ b/src/cribl_mgmt_plane/types/base64fileinput.py @@ -0,0 +1,43 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations + +import base64 +import io +from os import PathLike +from typing import IO, Any, Union + +from pydantic.functional_validators import BeforeValidator +from typing_extensions import Annotated + + +Base64FileInput = Union[IO[bytes], PathLike[str]] + + +def encode_base64_file_input(value: Any) -> Any: + """Convert PathLike or IO[bytes] inputs to a base64 string. All standard binary streams + that inherit from io.IOBase are handled. Other values pass through. + """ + if isinstance(value, (PathLike, io.IOBase)): + if isinstance(value, PathLike): + with open(value, "rb") as fh: + binary = fh.read() + else: + # Restore position after reading: pydantic may validate the same stream more than once. + position = value.tell() if value.seekable() else None + binary = value.read() + if position is not None: + value.seek(position) + if isinstance(binary, str): + binary = binary.encode() + if not isinstance(binary, (bytes, bytearray)): + raise TypeError( + f"Base64FileInput expected binary IO returning bytes; got {type(binary).__name__}" + ) + return base64.b64encode(binary).decode("ascii") + return value + + +# Non-str inputs are converted to base64 by the BeforeValidator at construction time. +# Callers can also pass a pre-encoded base64 str. +Base64EncodedString = Annotated[str, BeforeValidator(encode_base64_file_input)] diff --git a/src/cribl_mgmt_plane/utils/eventstreaming.py b/src/cribl_mgmt_plane/utils/eventstreaming.py index 3bdcd6d..a8d4fe5 100644 --- a/src/cribl_mgmt_plane/utils/eventstreaming.py +++ b/src/cribl_mgmt_plane/utils/eventstreaming.py @@ -7,6 +7,7 @@ Any, Callable, Generic, + List, TypeVar, Optional, Generator, @@ -53,6 +54,9 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def close(self): self._closed = True self.response.close() @@ -92,6 +96,9 @@ async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + + async def close(self): self._closed = True await self.response.aclose() @@ -114,6 +121,7 @@ class ServerEvent: b"\n\r", b"\n\n", ] +MAX_BOUNDARY_LEN = max(len(b) for b in MESSAGE_BOUNDARIES) UTF8_BOM = b"\xef\xbb\xbf" @@ -124,52 +132,56 @@ async def stream_events_async( sentinel: Optional[str] = None, data_required: bool = True, ) -> AsyncGenerator[T, None]: - buffer = bytearray() - position = 0 - event_id: Optional[str] = None - async for chunk in response.aiter_bytes(): - if len(buffer) == 0 and chunk.startswith(UTF8_BOM): - chunk = chunk[len(UTF8_BOM) :] - buffer += chunk - for i in range(position, len(buffer)): - char = buffer[i : i + 1] - seq: Optional[bytes] = None - if char in [b"\r", b"\n"]: - for boundary in MESSAGE_BOUNDARIES: - seq = _peek_sequence(i, buffer, boundary) - if seq is not None: - break - if seq is None: - continue - - block = buffer[position:i] - position = i + len(seq) - event, discard, event_id = _parse_event( - raw=block, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, - ) - if event is not None: - yield event - if discard: - await response.aclose() - return - - if position > 0: - buffer = buffer[position:] - position = 0 - - event, discard, _ = _parse_event( - raw=buffer, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, - ) - if event is not None: - yield event + try: + buffer = bytearray() + position = 0 + event_id: Optional[str] = None + async for chunk in response.aiter_bytes(): + if len(buffer) == 0 and chunk.startswith(UTF8_BOM): + chunk = chunk[len(UTF8_BOM) :] + old_len = len(buffer) + buffer += chunk + search_start = max(position, old_len - MAX_BOUNDARY_LEN + 1) + for i in range(search_start, len(buffer)): + char = buffer[i : i + 1] + seq: Optional[bytes] = None + if char in [b"\r", b"\n"]: + for boundary in MESSAGE_BOUNDARIES: + seq = _peek_sequence(i, buffer, boundary) + if seq is not None: + break + if seq is None: + continue + + block = buffer[position:i] + position = i + len(seq) + event, discard, event_id = _parse_event( + raw=block, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + if discard: + return + + if position > 0: + buffer = buffer[position:] + position = 0 + + event, discard, _ = _parse_event( + raw=buffer, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + finally: + await response.aclose() def stream_events( @@ -178,52 +190,56 @@ def stream_events( sentinel: Optional[str] = None, data_required: bool = True, ) -> Generator[T, None, None]: - buffer = bytearray() - position = 0 - event_id: Optional[str] = None - for chunk in response.iter_bytes(): - if len(buffer) == 0 and chunk.startswith(UTF8_BOM): - chunk = chunk[len(UTF8_BOM) :] - buffer += chunk - for i in range(position, len(buffer)): - char = buffer[i : i + 1] - seq: Optional[bytes] = None - if char in [b"\r", b"\n"]: - for boundary in MESSAGE_BOUNDARIES: - seq = _peek_sequence(i, buffer, boundary) - if seq is not None: - break - if seq is None: - continue - - block = buffer[position:i] - position = i + len(seq) - event, discard, event_id = _parse_event( - raw=block, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, - ) - if event is not None: - yield event - if discard: - response.close() - return - - if position > 0: - buffer = buffer[position:] - position = 0 - - event, discard, _ = _parse_event( - raw=buffer, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, - ) - if event is not None: - yield event + try: + buffer = bytearray() + position = 0 + event_id: Optional[str] = None + for chunk in response.iter_bytes(): + if len(buffer) == 0 and chunk.startswith(UTF8_BOM): + chunk = chunk[len(UTF8_BOM) :] + old_len = len(buffer) + buffer += chunk + search_start = max(position, old_len - MAX_BOUNDARY_LEN + 1) + for i in range(search_start, len(buffer)): + char = buffer[i : i + 1] + seq: Optional[bytes] = None + if char in [b"\r", b"\n"]: + for boundary in MESSAGE_BOUNDARIES: + seq = _peek_sequence(i, buffer, boundary) + if seq is not None: + break + if seq is None: + continue + + block = buffer[position:i] + position = i + len(seq) + event, discard, event_id = _parse_event( + raw=block, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + if discard: + return + + if position > 0: + buffer = buffer[position:] + position = 0 + + event, discard, _ = _parse_event( + raw=buffer, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, + ) + if event is not None: + yield event + finally: + response.close() def _parse_event( @@ -238,7 +254,7 @@ def _parse_event( lines = re.split(r"\r?\n|\r", block) publish = False event = ServerEvent() - data = "" + data_parts: List[str] = [] for line in lines: if not line: continue @@ -259,7 +275,7 @@ def _parse_event( event.event = value publish = True elif field == "data": - data += value + "\n" + data_parts.append(value) publish = True elif field == "id": publish = True @@ -271,16 +287,17 @@ def _parse_event( publish = True event.id = event_id + has_data = bool(data_parts) + data = "\n".join(data_parts) - if sentinel and data == f"{sentinel}\n": + if sentinel and has_data and data == sentinel: return None, True, event_id # Skip data-less events when data is required - if not data and publish and data_required: + if not has_data and publish and data_required: return None, False, event_id - if data: - data = data[:-1] + if has_data: try: event.data = json.loads(data) except json.JSONDecodeError: @@ -291,7 +308,7 @@ def _parse_event( out_dict = { k: v for k, v in asdict(event).items() - if v is not None or (k == "data" and data) + if v is not None or (k == "data" and has_data) } out = decoder(json.dumps(out_dict)) diff --git a/src/cribl_mgmt_plane/utils/forms.py b/src/cribl_mgmt_plane/utils/forms.py index f961e76..fdf0dc9 100644 --- a/src/cribl_mgmt_plane/utils/forms.py +++ b/src/cribl_mgmt_plane/utils/forms.py @@ -1,5 +1,6 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" +import io from typing import ( Any, Dict, @@ -103,6 +104,10 @@ def _extract_file_properties(file_obj: Any) -> Tuple[str, Any, Any]: if file_metadata.content: content = getattr(file_obj, file_field_name, None) + if isinstance(content, io.TextIOBase): + content = content.read().encode( + getattr(content, "encoding", None) or "utf-8" + ) elif file_field_name == "content_type": content_type = getattr(file_obj, file_field_name, None) else: diff --git a/src/cribl_mgmt_plane/utils/requestbodies.py b/src/cribl_mgmt_plane/utils/requestbodies.py index 1de32b6..591415a 100644 --- a/src/cribl_mgmt_plane/utils/requestbodies.py +++ b/src/cribl_mgmt_plane/utils/requestbodies.py @@ -46,6 +46,7 @@ def serialize_request_body( if re.match(r"^(application|text)\/([^+]+\+)*json.*", media_type) is not None: serialized_request_body.content = marshal_json(request_body, request_body_type) + elif re.match(r"^multipart\/.*", media_type) is not None: ( serialized_request_body.media_type, diff --git a/src/cribl_mgmt_plane/utils/retries.py b/src/cribl_mgmt_plane/utils/retries.py index af07d4e..bab2066 100644 --- a/src/cribl_mgmt_plane/utils/retries.py +++ b/src/cribl_mgmt_plane/utils/retries.py @@ -93,6 +93,21 @@ def _parse_retry_after_header(response: httpx.Response) -> Optional[int]: return None +def _parse_retry_after_ms_header(response: httpx.Response) -> Optional[int]: + retry_after_ms_header = response.headers.get("retry-after-ms") + if not retry_after_ms_header: + return None + + try: + milliseconds = float(retry_after_ms_header) + if milliseconds >= 0: + return round(milliseconds) + except (OverflowError, ValueError): + pass + + return None + + def _get_sleep_interval( exception: Exception, initial_interval: int, @@ -234,6 +249,10 @@ def retry_with_backoff( raise + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms sleep = _get_sleep_interval( exception, initial_interval, max_interval, exponent, retries ) @@ -264,6 +283,10 @@ async def retry_with_backoff_async( raise + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms sleep = _get_sleep_interval( exception, initial_interval, max_interval, exponent, retries ) diff --git a/src/cribl_mgmt_plane/utils/serializers.py b/src/cribl_mgmt_plane/utils/serializers.py index d2149f8..1031ed9 100644 --- a/src/cribl_mgmt_plane/utils/serializers.py +++ b/src/cribl_mgmt_plane/utils/serializers.py @@ -4,7 +4,7 @@ import functools import json import typing -from typing import Any, Dict, List, Tuple, Union, get_args +from typing import Any, Dict, Iterable, List, Mapping, Tuple, Union, get_args import typing_extensions from typing_extensions import get_origin @@ -113,10 +113,12 @@ def validate(c): def unmarshal_json(raw, typ: Any) -> Any: - return unmarshal(from_json(raw), typ) + return unmarshal(from_json(raw), typ, coerce_iterables=False) -def unmarshal(val, typ: Any) -> Any: +def unmarshal(val, typ: Any, coerce_iterables: bool = True) -> Any: + if coerce_iterables: + val = _coerce_iterables_for_type(val, typ) unmarshaller = create_model( "Unmarshaller", body=(typ, ...), @@ -193,9 +195,88 @@ def get_pydantic_model(data: Any, typ: Any) -> Any: if not _contains_pydantic_model(data): return unmarshal(data, typ) + return _coerce_iterables_for_type(data, typ) + + +def _coerce_iterables_for_type(data: Any, typ: Any) -> Any: + if data is None or isinstance(data, (BaseModel, Unset)): + return data + + typ = _resolve_type_alias(typ) + origin = get_origin(typ) + + if _is_annotated_type(origin): + args = get_args(typ) + return _coerce_iterables_for_type(data, args[0]) if args else data + + if is_union(origin): + for arg in (arg for arg in get_args(typ) if arg is not type(None)): + coerced = _coerce_iterables_for_type(data, arg) + if coerced is not data: + return coerced + return data + + if _is_list_type(typ): + item_type = get_args(typ)[0] if get_args(typ) else Any + if isinstance(data, (str, bytes, bytearray, Mapping)): + return data + if isinstance(data, Iterable): + return [_coerce_iterables_for_type(item, item_type) for item in data] + return data + + if _is_mapping_type(typ): + value_type = get_args(typ)[1] if len(get_args(typ)) > 1 else Any + if isinstance(data, Mapping): + return { + key: _coerce_iterables_for_type(value, value_type) + for key, value in data.items() + } + return data + + if _is_pydantic_model_type(typ) and isinstance(data, Mapping): + coerced = None + for field_name, field in typ.model_fields.items(): + field_type = field.annotation + for key in (field_name, field.alias): + if key is not None and key in data: + value = data[key] if coerced is None else coerced[key] + coerced_value = _coerce_iterables_for_type(value, field_type) + if coerced_value is not value: + if coerced is None: + coerced = dict(data) + coerced[key] = coerced_value + return coerced if coerced is not None else data + return data +def _resolve_type_alias(typ: Any) -> Any: + return getattr(typ, "__value__", typ) + + +def _is_annotated_type(origin: Any) -> bool: + return any( + origin is typing_obj + for typing_obj in _get_typing_objects_by_name_of("Annotated") + ) + + +def _is_list_type(typ: Any) -> bool: + typ = _resolve_type_alias(typ) + return typ is list or get_origin(typ) is list + + +def _is_mapping_type(typ: Any) -> bool: + typ = _resolve_type_alias(typ) + origin = get_origin(typ) + mapping_origin = get_origin(Mapping[Any, Any]) + return typ in (dict, Dict, Mapping) or origin in (dict, Mapping, mapping_origin) + + +def _is_pydantic_model_type(typ: Any) -> bool: + return isinstance(typ, type) and issubclass(typ, BaseModel) + + def _contains_pydantic_model(data: Any) -> bool: if isinstance(data, BaseModel): return True diff --git a/src/cribl_mgmt_plane/workspaces.py b/src/cribl_mgmt_plane/workspaces.py index 4d5efe1..68c5bf8 100644 --- a/src/cribl_mgmt_plane/workspaces.py +++ b/src/cribl_mgmt_plane/workspaces.py @@ -6,7 +6,7 @@ from cribl_mgmt_plane.types import OptionalNullable, UNSET from cribl_mgmt_plane.utils import get_security_from_env from cribl_mgmt_plane.utils.unmarshal_json_response import unmarshal_json_response -from typing import List, Mapping, Optional +from typing import Iterable, List, Mapping, Optional class Workspaces(BaseSDK): @@ -19,7 +19,7 @@ def create( workspace_id: str, alias: Optional[str] = None, description: Optional[str] = None, - tags: Optional[List[str]] = None, + tags: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -55,7 +55,7 @@ def create( workspace_id=workspace_id, alias=alias, description=description, - tags=tags, + tags=utils.unmarshal(tags, Optional[List[str]]), ), ) @@ -130,7 +130,7 @@ async def create_async( workspace_id: str, alias: Optional[str] = None, description: Optional[str] = None, - tags: Optional[List[str]] = None, + tags: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -166,7 +166,7 @@ async def create_async( workspace_id=workspace_id, alias=alias, description=description, - tags=tags, + tags=utils.unmarshal(tags, Optional[List[str]]), ), ) @@ -421,7 +421,7 @@ def update( workspace_id: str, alias: Optional[str] = None, description: Optional[str] = None, - tags: Optional[List[str]] = None, + tags: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -457,7 +457,7 @@ def update( workspace_patch_request_dto=models.WorkspacePatchRequestDTO( alias=alias, description=description, - tags=tags, + tags=utils.unmarshal(tags, Optional[List[str]]), ), ) @@ -532,7 +532,7 @@ async def update_async( workspace_id: str, alias: Optional[str] = None, description: Optional[str] = None, - tags: Optional[List[str]] = None, + tags: Optional[Iterable[str]] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -568,7 +568,7 @@ async def update_async( workspace_patch_request_dto=models.WorkspacePatchRequestDTO( alias=alias, description=description, - tags=tags, + tags=utils.unmarshal(tags, Optional[List[str]]), ), ) From fc74837e0d4ae4d04ca3471896320ca3ffd3365b Mon Sep 17 00:00:00 2001 From: "speakeasy-github[bot]" <128539517+speakeasy-github[bot]@users.noreply.github.com> Date: Sat, 27 Jun 2026 03:49:01 +0000 Subject: [PATCH 2/2] empty commit to trigger [run-tests] workflow