From ce58b950f9e51540fbe0bf86ec69168f47e17ecf Mon Sep 17 00:00:00 2001 From: etserend Date: Mon, 20 Jul 2026 17:52:54 -0500 Subject: [PATCH 1/3] fix(resources): preserve multipart file uploads in generated client (02ce8bb) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port of rungalileo/galileo-python@02ce8bb2 — fix: Preserve multipart file uploads in generated client (#624). - openapi.yaml: replace contentMediaType: application/octet-stream with format: binary for 7 Body_* multipart file fields so openapi-python-client generates File-typed attributes instead of plain str - scripts/import-openapi-yaml.sh: add yq patches for the 7 binary fields so the fix survives future openapi.yaml regenerations; tighten error handling - 6 generated Body_* models: update file fields from str → File, add BytesIO import, add File/FileTypes imports, call .to_tuple() in to_dict/to_multipart, construct File(payload=BytesIO(...)) in from_dict - tests/test_datasets.py: add two tests verifying File payloads round-trip through to_multipart() correctly --- openapi.yaml | 14 +-- scripts/import-openapi-yaml.sh | 10 +- ...ion_scorers_scorer_id_version_code_post.py | 15 ++- .../body_create_dataset_datasets_post.py | 96 ++++++++-------- ...art_scorers_llm_validate_multipart_post.py | 48 +++++--- ...aset_scorers_code_validate_dataset_post.py | 81 ++++++------- ...d_scorers_code_validate_log_record_post.py | 107 ++++++++---------- ..._code_scorer_scorers_code_validate_post.py | 66 +++++------ tests/test_datasets.py | 32 +++++- 9 files changed, 243 insertions(+), 226 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 97c3ebef..84da3370 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -14069,7 +14069,7 @@ components: properties: file: type: string - contentMediaType: application/octet-stream + format: binary title: File validation_result: type: string @@ -14102,7 +14102,7 @@ components: file: anyOf: - type: string - contentMediaType: application/octet-stream + format: binary - type: 'null' title: File copy_from_dataset_id: @@ -14173,14 +14173,14 @@ components: query_files: items: type: string - contentMediaType: application/octet-stream + format: binary type: array title: Query Files default: [] response_files: items: type: string - contentMediaType: application/octet-stream + format: binary type: array title: Response Files default: [] @@ -14192,7 +14192,7 @@ components: properties: file: type: string - contentMediaType: application/octet-stream + format: binary title: File dataset_id: type: string @@ -14242,7 +14242,7 @@ components: properties: file: type: string - contentMediaType: application/octet-stream + format: binary title: File log_stream_id: anyOf: @@ -14301,7 +14301,7 @@ components: properties: file: type: string - contentMediaType: application/octet-stream + format: binary title: File test_input: anyOf: diff --git a/scripts/import-openapi-yaml.sh b/scripts/import-openapi-yaml.sh index 6c17e0ff..4aa25af2 100755 --- a/scripts/import-openapi-yaml.sh +++ b/scripts/import-openapi-yaml.sh @@ -43,16 +43,16 @@ curl -s "${HOST_URL}/openapi.json" | poetry run python -c 'import sys, json, yam # - NotNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____.title = "NotNodeLogRecordsFilter" # - OrNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____.title = "OrNodeLogRecordsFilter" # - StepType.enum += ["control"] |= unique: server omits this value from the spec but returns it for some log-stream columns (sc-62628); unique deduplicates if the server spec is later fixed +# - multipart/form-data binary fields: contentMediaType = "application/octet-stream" -> format = "binary" # # If you run into related issues with the auto-generate-api-client.sh script, add the openapi.yaml patches here. # Apply all patches using yq in a single command -poetry run python -m yq --in-place -Y '.components.schemas.api__schemas__project_v2__GetProjectsPaginatedResponse.title = "GetProjectsPaginatedResponseV2" | .components.schemas.galileo_core__schemas__shared__message__Message.title = "MessagesListItem" | .components.schemas.galileo_core__schemas__shared__message_role__MessageRole.title = "MessagesListItemRole" | .components.schemas.ListDatasetParams.properties.sort.default = "None" | .components.schemas.ListPromptTemplateParams.properties.sort.default = "None" | .components.schemas.ProjectCollectionParams.properties.sort.default = "None" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.title = "CoreScorerName" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.enum |= unique | .paths["/llm_integrations/projects/{project_id}/runs/{run_id}"].get.responses["200"].content["application/json"].schema.title = "GetRunIntegrationsResponse" | .components.schemas.Document.properties.content = .components.schemas.Document.properties.page_content | del(.components.schemas.Document.properties.page_content) | .components.schemas.Document.properties.content.title = "Content" | .components.schemas.Document.required = ["content"] | .components.schemas["AndNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "AndNodeLogRecordsFilter" | .components.schemas["FilterExpression_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterExpressionLogRecordsFilter" | .components.schemas["FilterLeaf_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterLeafLogRecordsFilter" | .components.schemas["NotNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "NotNodeLogRecordsFilter" | .components.schemas["OrNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "OrNodeLogRecordsFilter" | .components.schemas.StepType.enum += ["control"] | .components.schemas.StepType.enum |= unique' "$HOME_DIR/openapi.yaml" +poetry run python -m yq --in-place -Y '.components.schemas.api__schemas__project_v2__GetProjectsPaginatedResponse.title = "GetProjectsPaginatedResponseV2" | .components.schemas.galileo_core__schemas__shared__message__Message.title = "MessagesListItem" | .components.schemas.galileo_core__schemas__shared__message_role__MessageRole.title = "MessagesListItemRole" | .components.schemas.ListDatasetParams.properties.sort.default = "None" | .components.schemas.ListPromptTemplateParams.properties.sort.default = "None" | .components.schemas.ProjectCollectionParams.properties.sort.default = "None" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.title = "CoreScorerName" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.enum |= unique | .paths["/llm_integrations/projects/{project_id}/runs/{run_id}"].get.responses["200"].content["application/json"].schema.title = "GetRunIntegrationsResponse" | .components.schemas.Document.properties.content = .components.schemas.Document.properties.page_content | del(.components.schemas.Document.properties.page_content) | .components.schemas.Document.properties.content.title = "Content" | .components.schemas.Document.required = ["content"] | .components.schemas["AndNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "AndNodeLogRecordsFilter" | .components.schemas["FilterExpression_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterExpressionLogRecordsFilter" | .components.schemas["FilterLeaf_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterLeafLogRecordsFilter" | .components.schemas["NotNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "NotNodeLogRecordsFilter" | .components.schemas["OrNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "OrNodeLogRecordsFilter" | .components.schemas.StepType.enum += ["control"] | .components.schemas.StepType.enum |= unique | .components.schemas["Body_create_code_scorer_version_scorers__scorer_id__version_code_post"].properties.file.format = "binary" | del(.components.schemas["Body_create_code_scorer_version_scorers__scorer_id__version_code_post"].properties.file.contentMediaType) | .components.schemas["Body_create_dataset_datasets_post"].properties.file.anyOf[0].format = "binary" | del(.components.schemas["Body_create_dataset_datasets_post"].properties.file.anyOf[0].contentMediaType) | .components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.query_files.items.format = "binary" | del(.components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.query_files.items.contentMediaType) | .components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.response_files.items.format = "binary" | del(.components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.response_files.items.contentMediaType) | .components.schemas["Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post"].properties.file.contentMediaType) | .components.schemas["Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post"].properties.file.contentMediaType) | .components.schemas["Body_validate_code_scorer_scorers_code_validate_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_scorers_code_validate_post"].properties.file.contentMediaType)' "$HOME_DIR/openapi.yaml" -# Check if the command was successful -if [ $? -eq 0 ]; then - echo "OpenAPI YAML saved to $HOME_DIR/openapi.yaml" -else +if [ $? -ne 0 ]; then echo "Failed to fetch and convert OpenAPI JSON" exit 1 fi + +echo "OpenAPI YAML saved to $HOME_DIR/openapi.yaml" diff --git a/src/splunk_ao/resources/models/body_create_code_scorer_version_scorers_scorer_id_version_code_post.py b/src/splunk_ao/resources/models/body_create_code_scorer_version_scorers_scorer_id_version_code_post.py index d37522b9..c70d0322 100644 --- a/src/splunk_ao/resources/models/body_create_code_scorer_version_scorers_scorer_id_version_code_post.py +++ b/src/splunk_ao/resources/models/body_create_code_scorer_version_scorers_scorer_id_version_code_post.py @@ -1,10 +1,12 @@ from collections.abc import Mapping +from io import BytesIO from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field from .. import types +from ..types import File T = TypeVar("T", bound="BodyCreateCodeScorerVersionScorersScorerIdVersionCodePost") @@ -12,17 +14,18 @@ @_attrs_define class BodyCreateCodeScorerVersionScorersScorerIdVersionCodePost: """ - Attributes: + Attributes + ---------- file (str): - validation_result (str): Pre-validated result as JSON string from the validate endpoint + validation_result (str): Pre-validated result as JSON string from the validate endpoint. """ - file: str + file: File validation_result: str additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - file = self.file + file = self.file.to_tuple() validation_result = self.validation_result @@ -35,7 +38,7 @@ def to_dict(self) -> dict[str, Any]: def to_multipart(self) -> types.RequestFiles: files: types.RequestFiles = [] - files.append(("file", (None, str(self.file).encode(), "text/plain"))) + files.append(("file", self.file.to_tuple())) files.append(("validation_result", (None, str(self.validation_result).encode(), "text/plain"))) @@ -47,7 +50,7 @@ def to_multipart(self) -> types.RequestFiles: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - file = d.pop("file") + file = File(payload=BytesIO(d.pop("file"))) validation_result = d.pop("validation_result") diff --git a/src/splunk_ao/resources/models/body_create_dataset_datasets_post.py b/src/splunk_ao/resources/models/body_create_dataset_datasets_post.py index 6a8e93c3..f62b25d8 100644 --- a/src/splunk_ao/resources/models/body_create_dataset_datasets_post.py +++ b/src/splunk_ao/resources/models/body_create_dataset_datasets_post.py @@ -1,11 +1,12 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union, cast +from io import BytesIO +from typing import Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field from .. import types -from ..types import UNSET, Unset +from ..types import UNSET, File, FileTypes, Unset T = TypeVar("T", bound="BodyCreateDatasetDatasetsPost") @@ -13,27 +14,28 @@ @_attrs_define class BodyCreateDatasetDatasetsPost: """ - Attributes: + Attributes + ---------- draft (Union[Unset, bool]): Default: False. hidden (Union[Unset, bool]): Default: False. name (Union[None, Unset, str]): append_suffix_if_duplicate (Union[Unset, bool]): Default: False. - file (Union[None, Unset, str]): + file (Union[File, None, Unset]): copy_from_dataset_id (Union[None, Unset, str]): copy_from_dataset_version_index (Union[None, Unset, int]): project_id (Union[None, Unset, str]): column_mapping (Union[None, Unset, str]): """ - draft: Union[Unset, bool] = False - hidden: Union[Unset, bool] = False - name: Union[None, Unset, str] = UNSET - append_suffix_if_duplicate: Union[Unset, bool] = False - file: Union[None, Unset, str] = UNSET - copy_from_dataset_id: Union[None, Unset, str] = UNSET - copy_from_dataset_version_index: Union[None, Unset, int] = UNSET - project_id: Union[None, Unset, str] = UNSET - column_mapping: Union[None, Unset, str] = UNSET + draft: Unset | bool = False + hidden: Unset | bool = False + name: None | Unset | str = UNSET + append_suffix_if_duplicate: Unset | bool = False + file: File | None | Unset = UNSET + copy_from_dataset_id: None | Unset | str = UNSET + copy_from_dataset_version_index: None | Unset | int = UNSET + project_id: None | Unset | str = UNSET + column_mapping: None | Unset | str = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -41,43 +43,33 @@ def to_dict(self) -> dict[str, Any]: hidden = self.hidden - name: Union[None, Unset, str] - if isinstance(self.name, Unset): - name = UNSET - else: - name = self.name + name: None | Unset | str + name = UNSET if isinstance(self.name, Unset) else self.name append_suffix_if_duplicate = self.append_suffix_if_duplicate - file: Union[None, Unset, str] + file: FileTypes | None | Unset if isinstance(self.file, Unset): file = UNSET + elif isinstance(self.file, File): + file = self.file.to_tuple() else: file = self.file - copy_from_dataset_id: Union[None, Unset, str] - if isinstance(self.copy_from_dataset_id, Unset): - copy_from_dataset_id = UNSET - else: - copy_from_dataset_id = self.copy_from_dataset_id + copy_from_dataset_id: None | Unset | str + copy_from_dataset_id = UNSET if isinstance(self.copy_from_dataset_id, Unset) else self.copy_from_dataset_id - copy_from_dataset_version_index: Union[None, Unset, int] + copy_from_dataset_version_index: None | Unset | int if isinstance(self.copy_from_dataset_version_index, Unset): copy_from_dataset_version_index = UNSET else: copy_from_dataset_version_index = self.copy_from_dataset_version_index - project_id: Union[None, Unset, str] - if isinstance(self.project_id, Unset): - project_id = UNSET - else: - project_id = self.project_id + project_id: None | Unset | str + project_id = UNSET if isinstance(self.project_id, Unset) else self.project_id - column_mapping: Union[None, Unset, str] - if isinstance(self.column_mapping, Unset): - column_mapping = UNSET - else: - column_mapping = self.column_mapping + column_mapping: None | Unset | str + column_mapping = UNSET if isinstance(self.column_mapping, Unset) else self.column_mapping field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) @@ -124,8 +116,8 @@ def to_multipart(self) -> types.RequestFiles: ) if not isinstance(self.file, Unset): - if isinstance(self.file, str): - files.append(("file", (None, str(self.file).encode(), "text/plain"))) + if isinstance(self.file, File): + files.append(("file", self.file.to_tuple())) else: files.append(("file", (None, str(self.file).encode(), "text/plain"))) @@ -175,61 +167,67 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: hidden = d.pop("hidden", UNSET) - def _parse_name(data: object) -> Union[None, Unset, str]: + def _parse_name(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) name = _parse_name(d.pop("name", UNSET)) append_suffix_if_duplicate = d.pop("append_suffix_if_duplicate", UNSET) - def _parse_file(data: object) -> Union[None, Unset, str]: + def _parse_file(data: object) -> File | None | Unset: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + try: + if not isinstance(data, bytes): + raise TypeError() + return File(payload=BytesIO(data)) + except: # noqa: E722 + pass + return cast(File | None | Unset, data) file = _parse_file(d.pop("file", UNSET)) - def _parse_copy_from_dataset_id(data: object) -> Union[None, Unset, str]: + def _parse_copy_from_dataset_id(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) copy_from_dataset_id = _parse_copy_from_dataset_id(d.pop("copy_from_dataset_id", UNSET)) - def _parse_copy_from_dataset_version_index(data: object) -> Union[None, Unset, int]: + def _parse_copy_from_dataset_version_index(data: object) -> None | Unset | int: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, int], data) + return cast(None | Unset | int, data) copy_from_dataset_version_index = _parse_copy_from_dataset_version_index( d.pop("copy_from_dataset_version_index", UNSET) ) - def _parse_project_id(data: object) -> Union[None, Unset, str]: + def _parse_project_id(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) project_id = _parse_project_id(d.pop("project_id", UNSET)) - def _parse_column_mapping(data: object) -> Union[None, Unset, str]: + def _parse_column_mapping(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) column_mapping = _parse_column_mapping(d.pop("column_mapping", UNSET)) diff --git a/src/splunk_ao/resources/models/body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.py b/src/splunk_ao/resources/models/body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.py index d1081090..88b03f32 100644 --- a/src/splunk_ao/resources/models/body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.py +++ b/src/splunk_ao/resources/models/body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.py @@ -1,11 +1,12 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union, cast +from io import BytesIO +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field from .. import types -from ..types import UNSET, Unset +from ..types import UNSET, File, FileTypes, Unset T = TypeVar("T", bound="BodyManualLlmValidateMultipartScorersLlmValidateMultipartPost") @@ -13,27 +14,34 @@ @_attrs_define class BodyManualLlmValidateMultipartScorersLlmValidateMultipartPost: """ - Attributes: + Attributes + ---------- body (str): JSON-encoded GeneratedScorerValidationRequest - query_files (Union[Unset, list[str]]): - response_files (Union[Unset, list[str]]): + query_files (Union[Unset, list[File]]): + response_files (Union[Unset, list[File]]): """ body: str - query_files: Union[Unset, list[str]] = UNSET - response_files: Union[Unset, list[str]] = UNSET + query_files: Unset | list[File] = UNSET + response_files: Unset | list[File] = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: body = self.body - query_files: Union[Unset, list[str]] = UNSET + query_files: Unset | list[FileTypes] = UNSET if not isinstance(self.query_files, Unset): - query_files = self.query_files + query_files = [] + for query_files_item_data in self.query_files: + query_files_item = query_files_item_data.to_tuple() + query_files.append(query_files_item) - response_files: Union[Unset, list[str]] = UNSET + response_files: Unset | list[FileTypes] = UNSET if not isinstance(self.response_files, Unset): - response_files = self.response_files + response_files = [] + for response_files_item_data in self.response_files: + response_files_item = response_files_item_data.to_tuple() + response_files.append(response_files_item) field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) @@ -52,11 +60,11 @@ def to_multipart(self) -> types.RequestFiles: if not isinstance(self.query_files, Unset): for query_files_item_element in self.query_files: - files.append(("query_files", (None, str(query_files_item_element).encode(), "text/plain"))) + files.append(("query_files", query_files_item_element.to_tuple())) if not isinstance(self.response_files, Unset): for response_files_item_element in self.response_files: - files.append(("response_files", (None, str(response_files_item_element).encode(), "text/plain"))) + files.append(("response_files", response_files_item_element.to_tuple())) for prop_name, prop in self.additional_properties.items(): files.append((prop_name, (None, str(prop).encode(), "text/plain"))) @@ -68,9 +76,17 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) body = d.pop("body") - query_files = cast(list[str], d.pop("query_files", UNSET)) - - response_files = cast(list[str], d.pop("response_files", UNSET)) + query_files = [] + _query_files = d.pop("query_files", UNSET) + for query_files_item_data in _query_files or []: + query_files_item = File(payload=BytesIO(query_files_item_data)) + query_files.append(query_files_item) + + response_files = [] + _response_files = d.pop("response_files", UNSET) + for response_files_item_data in _response_files or []: + response_files_item = File(payload=BytesIO(response_files_item_data)) + response_files.append(response_files_item) body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post = cls( body=body, query_files=query_files, response_files=response_files diff --git a/src/splunk_ao/resources/models/body_validate_code_scorer_dataset_scorers_code_validate_dataset_post.py b/src/splunk_ao/resources/models/body_validate_code_scorer_dataset_scorers_code_validate_dataset_post.py index b0a1f614..ab8c9897 100644 --- a/src/splunk_ao/resources/models/body_validate_code_scorer_dataset_scorers_code_validate_dataset_post.py +++ b/src/splunk_ao/resources/models/body_validate_code_scorer_dataset_scorers_code_validate_dataset_post.py @@ -1,12 +1,13 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union, cast +from io import BytesIO +from typing import Any, TypeVar, cast from uuid import UUID from attrs import define as _attrs_define from attrs import field as _attrs_field from .. import types -from ..types import UNSET, Unset +from ..types import UNSET, File, Unset T = TypeVar("T", bound="BodyValidateCodeScorerDatasetScorersCodeValidateDatasetPost") @@ -14,8 +15,9 @@ @_attrs_define class BodyValidateCodeScorerDatasetScorersCodeValidateDatasetPost: """ - Attributes: - file (str): + Attributes + ---------- + file (File): dataset_id (UUID): dataset_version_index (Union[None, Unset, int]): limit (Union[Unset, int]): Default: 100. @@ -25,36 +27,30 @@ class BodyValidateCodeScorerDatasetScorersCodeValidateDatasetPost: score_type (Union[None, Unset, str]): """ - file: str + file: File dataset_id: UUID - dataset_version_index: Union[None, Unset, int] = UNSET - limit: Union[Unset, int] = 100 - starting_token: Union[None, Unset, int] = UNSET - required_scorers: Union[None, Unset, list[str], str] = UNSET - scoreable_node_types: Union[None, Unset, list[str], str] = UNSET - score_type: Union[None, Unset, str] = UNSET + dataset_version_index: None | Unset | int = UNSET + limit: Unset | int = 100 + starting_token: None | Unset | int = UNSET + required_scorers: None | Unset | list[str] | str = UNSET + scoreable_node_types: None | Unset | list[str] | str = UNSET + score_type: None | Unset | str = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - file = self.file + file = self.file.to_tuple() dataset_id = str(self.dataset_id) - dataset_version_index: Union[None, Unset, int] - if isinstance(self.dataset_version_index, Unset): - dataset_version_index = UNSET - else: - dataset_version_index = self.dataset_version_index + dataset_version_index: None | Unset | int + dataset_version_index = UNSET if isinstance(self.dataset_version_index, Unset) else self.dataset_version_index limit = self.limit - starting_token: Union[None, Unset, int] - if isinstance(self.starting_token, Unset): - starting_token = UNSET - else: - starting_token = self.starting_token + starting_token: None | Unset | int + starting_token = UNSET if isinstance(self.starting_token, Unset) else self.starting_token - required_scorers: Union[None, Unset, list[str], str] + required_scorers: None | Unset | list[str] | str if isinstance(self.required_scorers, Unset): required_scorers = UNSET elif isinstance(self.required_scorers, list): @@ -63,7 +59,7 @@ def to_dict(self) -> dict[str, Any]: else: required_scorers = self.required_scorers - scoreable_node_types: Union[None, Unset, list[str], str] + scoreable_node_types: None | Unset | list[str] | str if isinstance(self.scoreable_node_types, Unset): scoreable_node_types = UNSET elif isinstance(self.scoreable_node_types, list): @@ -72,11 +68,8 @@ def to_dict(self) -> dict[str, Any]: else: scoreable_node_types = self.scoreable_node_types - score_type: Union[None, Unset, str] - if isinstance(self.score_type, Unset): - score_type = UNSET - else: - score_type = self.score_type + score_type: None | Unset | str + score_type = UNSET if isinstance(self.score_type, Unset) else self.score_type field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) @@ -99,7 +92,7 @@ def to_dict(self) -> dict[str, Any]: def to_multipart(self) -> types.RequestFiles: files: types.RequestFiles = [] - files.append(("file", (None, str(self.file).encode(), "text/plain"))) + files.append(("file", self.file.to_tuple())) files.append(("dataset_id", (None, str(self.dataset_id), "text/plain"))) @@ -157,31 +150,31 @@ def to_multipart(self) -> types.RequestFiles: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - file = d.pop("file") + file = File(payload=BytesIO(d.pop("file"))) dataset_id = UUID(d.pop("dataset_id")) - def _parse_dataset_version_index(data: object) -> Union[None, Unset, int]: + def _parse_dataset_version_index(data: object) -> None | Unset | int: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, int], data) + return cast(None | Unset | int, data) dataset_version_index = _parse_dataset_version_index(d.pop("dataset_version_index", UNSET)) limit = d.pop("limit", UNSET) - def _parse_starting_token(data: object) -> Union[None, Unset, int]: + def _parse_starting_token(data: object) -> None | Unset | int: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, int], data) + return cast(None | Unset | int, data) starting_token = _parse_starting_token(d.pop("starting_token", UNSET)) - def _parse_required_scorers(data: object) -> Union[None, Unset, list[str], str]: + def _parse_required_scorers(data: object) -> None | Unset | list[str] | str: if data is None: return data if isinstance(data, Unset): @@ -189,16 +182,15 @@ def _parse_required_scorers(data: object) -> Union[None, Unset, list[str], str]: try: if not isinstance(data, list): raise TypeError() - required_scorers_type_1 = cast(list[str], data) + return cast(list[str], data) - return required_scorers_type_1 except: # noqa: E722 pass - return cast(Union[None, Unset, list[str], str], data) + return cast(None | Unset | list[str] | str, data) required_scorers = _parse_required_scorers(d.pop("required_scorers", UNSET)) - def _parse_scoreable_node_types(data: object) -> Union[None, Unset, list[str], str]: + def _parse_scoreable_node_types(data: object) -> None | Unset | list[str] | str: if data is None: return data if isinstance(data, Unset): @@ -206,21 +198,20 @@ def _parse_scoreable_node_types(data: object) -> Union[None, Unset, list[str], s try: if not isinstance(data, list): raise TypeError() - scoreable_node_types_type_1 = cast(list[str], data) + return cast(list[str], data) - return scoreable_node_types_type_1 except: # noqa: E722 pass - return cast(Union[None, Unset, list[str], str], data) + return cast(None | Unset | list[str] | str, data) scoreable_node_types = _parse_scoreable_node_types(d.pop("scoreable_node_types", UNSET)) - def _parse_score_type(data: object) -> Union[None, Unset, str]: + def _parse_score_type(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) score_type = _parse_score_type(d.pop("score_type", UNSET)) diff --git a/src/splunk_ao/resources/models/body_validate_code_scorer_log_record_scorers_code_validate_log_record_post.py b/src/splunk_ao/resources/models/body_validate_code_scorer_log_record_scorers_code_validate_log_record_post.py index 0ecf248b..7ac33f50 100644 --- a/src/splunk_ao/resources/models/body_validate_code_scorer_log_record_scorers_code_validate_log_record_post.py +++ b/src/splunk_ao/resources/models/body_validate_code_scorer_log_record_scorers_code_validate_log_record_post.py @@ -1,11 +1,12 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union, cast +from io import BytesIO +from typing import Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field from .. import types -from ..types import UNSET, Unset +from ..types import UNSET, File, Unset T = TypeVar("T", bound="BodyValidateCodeScorerLogRecordScorersCodeValidateLogRecordPost") @@ -13,8 +14,9 @@ @_attrs_define class BodyValidateCodeScorerLogRecordScorersCodeValidateLogRecordPost: """ - Attributes: - file (str): + Attributes + ---------- + file (File): log_stream_id (Union[None, Unset, str]): experiment_id (Union[None, Unset, str]): limit (Union[Unset, int]): Default: 100. @@ -25,53 +27,38 @@ class BodyValidateCodeScorerLogRecordScorersCodeValidateLogRecordPost: scoreable_node_types (Union[None, Unset, list[str], str]): """ - file: str - log_stream_id: Union[None, Unset, str] = UNSET - experiment_id: Union[None, Unset, str] = UNSET - limit: Union[Unset, int] = 100 - starting_token: Union[None, Unset, int] = UNSET - filters: Union[None, Unset, str] = UNSET - sort: Union[None, Unset, str] = UNSET - required_scorers: Union[None, Unset, list[str], str] = UNSET - scoreable_node_types: Union[None, Unset, list[str], str] = UNSET + file: File + log_stream_id: None | Unset | str = UNSET + experiment_id: None | Unset | str = UNSET + limit: Unset | int = 100 + starting_token: None | Unset | int = UNSET + filters: None | Unset | str = UNSET + sort: None | Unset | str = UNSET + required_scorers: None | Unset | list[str] | str = UNSET + scoreable_node_types: None | Unset | list[str] | str = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - file = self.file + file = self.file.to_tuple() - log_stream_id: Union[None, Unset, str] - if isinstance(self.log_stream_id, Unset): - log_stream_id = UNSET - else: - log_stream_id = self.log_stream_id + log_stream_id: None | Unset | str + log_stream_id = UNSET if isinstance(self.log_stream_id, Unset) else self.log_stream_id - experiment_id: Union[None, Unset, str] - if isinstance(self.experiment_id, Unset): - experiment_id = UNSET - else: - experiment_id = self.experiment_id + experiment_id: None | Unset | str + experiment_id = UNSET if isinstance(self.experiment_id, Unset) else self.experiment_id limit = self.limit - starting_token: Union[None, Unset, int] - if isinstance(self.starting_token, Unset): - starting_token = UNSET - else: - starting_token = self.starting_token + starting_token: None | Unset | int + starting_token = UNSET if isinstance(self.starting_token, Unset) else self.starting_token - filters: Union[None, Unset, str] - if isinstance(self.filters, Unset): - filters = UNSET - else: - filters = self.filters + filters: None | Unset | str + filters = UNSET if isinstance(self.filters, Unset) else self.filters - sort: Union[None, Unset, str] - if isinstance(self.sort, Unset): - sort = UNSET - else: - sort = self.sort + sort: None | Unset | str + sort = UNSET if isinstance(self.sort, Unset) else self.sort - required_scorers: Union[None, Unset, list[str], str] + required_scorers: None | Unset | list[str] | str if isinstance(self.required_scorers, Unset): required_scorers = UNSET elif isinstance(self.required_scorers, list): @@ -80,7 +67,7 @@ def to_dict(self) -> dict[str, Any]: else: required_scorers = self.required_scorers - scoreable_node_types: Union[None, Unset, list[str], str] + scoreable_node_types: None | Unset | list[str] | str if isinstance(self.scoreable_node_types, Unset): scoreable_node_types = UNSET elif isinstance(self.scoreable_node_types, list): @@ -114,7 +101,7 @@ def to_dict(self) -> dict[str, Any]: def to_multipart(self) -> types.RequestFiles: files: types.RequestFiles = [] - files.append(("file", (None, str(self.file).encode(), "text/plain"))) + files.append(("file", self.file.to_tuple())) if not isinstance(self.log_stream_id, Unset): if isinstance(self.log_stream_id, str): @@ -182,56 +169,56 @@ def to_multipart(self) -> types.RequestFiles: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - file = d.pop("file") + file = File(payload=BytesIO(d.pop("file"))) - def _parse_log_stream_id(data: object) -> Union[None, Unset, str]: + def _parse_log_stream_id(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) log_stream_id = _parse_log_stream_id(d.pop("log_stream_id", UNSET)) - def _parse_experiment_id(data: object) -> Union[None, Unset, str]: + def _parse_experiment_id(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) experiment_id = _parse_experiment_id(d.pop("experiment_id", UNSET)) limit = d.pop("limit", UNSET) - def _parse_starting_token(data: object) -> Union[None, Unset, int]: + def _parse_starting_token(data: object) -> None | Unset | int: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, int], data) + return cast(None | Unset | int, data) starting_token = _parse_starting_token(d.pop("starting_token", UNSET)) - def _parse_filters(data: object) -> Union[None, Unset, str]: + def _parse_filters(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) filters = _parse_filters(d.pop("filters", UNSET)) - def _parse_sort(data: object) -> Union[None, Unset, str]: + def _parse_sort(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) sort = _parse_sort(d.pop("sort", UNSET)) - def _parse_required_scorers(data: object) -> Union[None, Unset, list[str], str]: + def _parse_required_scorers(data: object) -> None | Unset | list[str] | str: if data is None: return data if isinstance(data, Unset): @@ -239,16 +226,15 @@ def _parse_required_scorers(data: object) -> Union[None, Unset, list[str], str]: try: if not isinstance(data, list): raise TypeError() - required_scorers_type_1 = cast(list[str], data) + return cast(list[str], data) - return required_scorers_type_1 except: # noqa: E722 pass - return cast(Union[None, Unset, list[str], str], data) + return cast(None | Unset | list[str] | str, data) required_scorers = _parse_required_scorers(d.pop("required_scorers", UNSET)) - def _parse_scoreable_node_types(data: object) -> Union[None, Unset, list[str], str]: + def _parse_scoreable_node_types(data: object) -> None | Unset | list[str] | str: if data is None: return data if isinstance(data, Unset): @@ -256,12 +242,11 @@ def _parse_scoreable_node_types(data: object) -> Union[None, Unset, list[str], s try: if not isinstance(data, list): raise TypeError() - scoreable_node_types_type_1 = cast(list[str], data) + return cast(list[str], data) - return scoreable_node_types_type_1 except: # noqa: E722 pass - return cast(Union[None, Unset, list[str], str], data) + return cast(None | Unset | list[str] | str, data) scoreable_node_types = _parse_scoreable_node_types(d.pop("scoreable_node_types", UNSET)) diff --git a/src/splunk_ao/resources/models/body_validate_code_scorer_scorers_code_validate_post.py b/src/splunk_ao/resources/models/body_validate_code_scorer_scorers_code_validate_post.py index b51fe678..a13efba7 100644 --- a/src/splunk_ao/resources/models/body_validate_code_scorer_scorers_code_validate_post.py +++ b/src/splunk_ao/resources/models/body_validate_code_scorer_scorers_code_validate_post.py @@ -1,11 +1,12 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union, cast +from io import BytesIO +from typing import Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field from .. import types -from ..types import UNSET, Unset +from ..types import UNSET, File, Unset T = TypeVar("T", bound="BodyValidateCodeScorerScorersCodeValidatePost") @@ -13,37 +14,32 @@ @_attrs_define class BodyValidateCodeScorerScorersCodeValidatePost: """ - Attributes: - file (str): + Attributes + ---------- + file (File): test_input (Union[None, Unset, str]): test_output (Union[None, Unset, str]): required_scorers (Union[None, Unset, list[str], str]): scoreable_node_types (Union[None, Unset, list[str], str]): """ - file: str - test_input: Union[None, Unset, str] = UNSET - test_output: Union[None, Unset, str] = UNSET - required_scorers: Union[None, Unset, list[str], str] = UNSET - scoreable_node_types: Union[None, Unset, list[str], str] = UNSET + file: File + test_input: None | Unset | str = UNSET + test_output: None | Unset | str = UNSET + required_scorers: None | Unset | list[str] | str = UNSET + scoreable_node_types: None | Unset | list[str] | str = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - file = self.file + file = self.file.to_tuple() - test_input: Union[None, Unset, str] - if isinstance(self.test_input, Unset): - test_input = UNSET - else: - test_input = self.test_input + test_input: None | Unset | str + test_input = UNSET if isinstance(self.test_input, Unset) else self.test_input - test_output: Union[None, Unset, str] - if isinstance(self.test_output, Unset): - test_output = UNSET - else: - test_output = self.test_output + test_output: None | Unset | str + test_output = UNSET if isinstance(self.test_output, Unset) else self.test_output - required_scorers: Union[None, Unset, list[str], str] + required_scorers: None | Unset | list[str] | str if isinstance(self.required_scorers, Unset): required_scorers = UNSET elif isinstance(self.required_scorers, list): @@ -52,7 +48,7 @@ def to_dict(self) -> dict[str, Any]: else: required_scorers = self.required_scorers - scoreable_node_types: Union[None, Unset, list[str], str] + scoreable_node_types: None | Unset | list[str] | str if isinstance(self.scoreable_node_types, Unset): scoreable_node_types = UNSET elif isinstance(self.scoreable_node_types, list): @@ -78,7 +74,7 @@ def to_dict(self) -> dict[str, Any]: def to_multipart(self) -> types.RequestFiles: files: types.RequestFiles = [] - files.append(("file", (None, str(self.file).encode(), "text/plain"))) + files.append(("file", self.file.to_tuple())) if not isinstance(self.test_input, Unset): if isinstance(self.test_input, str): @@ -125,27 +121,27 @@ def to_multipart(self) -> types.RequestFiles: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - file = d.pop("file") + file = File(payload=BytesIO(d.pop("file"))) - def _parse_test_input(data: object) -> Union[None, Unset, str]: + def _parse_test_input(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) test_input = _parse_test_input(d.pop("test_input", UNSET)) - def _parse_test_output(data: object) -> Union[None, Unset, str]: + def _parse_test_output(data: object) -> None | Unset | str: if data is None: return data if isinstance(data, Unset): return data - return cast(Union[None, Unset, str], data) + return cast(None | Unset | str, data) test_output = _parse_test_output(d.pop("test_output", UNSET)) - def _parse_required_scorers(data: object) -> Union[None, Unset, list[str], str]: + def _parse_required_scorers(data: object) -> None | Unset | list[str] | str: if data is None: return data if isinstance(data, Unset): @@ -153,16 +149,15 @@ def _parse_required_scorers(data: object) -> Union[None, Unset, list[str], str]: try: if not isinstance(data, list): raise TypeError() - required_scorers_type_1 = cast(list[str], data) + return cast(list[str], data) - return required_scorers_type_1 except: # noqa: E722 pass - return cast(Union[None, Unset, list[str], str], data) + return cast(None | Unset | list[str] | str, data) required_scorers = _parse_required_scorers(d.pop("required_scorers", UNSET)) - def _parse_scoreable_node_types(data: object) -> Union[None, Unset, list[str], str]: + def _parse_scoreable_node_types(data: object) -> None | Unset | list[str] | str: if data is None: return data if isinstance(data, Unset): @@ -170,12 +165,11 @@ def _parse_scoreable_node_types(data: object) -> Union[None, Unset, list[str], s try: if not isinstance(data, list): raise TypeError() - scoreable_node_types_type_1 = cast(list[str], data) + return cast(list[str], data) - return scoreable_node_types_type_1 except: # noqa: E722 pass - return cast(Union[None, Unset, list[str], str], data) + return cast(None | Unset | list[str] | str, data) scoreable_node_types = _parse_scoreable_node_types(d.pop("scoreable_node_types", UNSET)) diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 410d33c3..e50dc232 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -21,7 +21,9 @@ list_dataset_projects, ) from splunk_ao.resources.models import ( + BodyCreateCodeScorerVersionScorersScorerIdVersionCodePost, BodyCreateDatasetDatasetsPost, + BodyValidateCodeScorerScorersCodeValidatePost, DatasetContent, DatasetDB, DatasetFormat, @@ -38,7 +40,7 @@ from splunk_ao.resources.models.dataset_row import DatasetRow from splunk_ao.resources.models.dataset_row_values_dict import DatasetRowValuesDict from splunk_ao.resources.models.http_validation_error import HTTPValidationError -from splunk_ao.resources.types import UNSET, Response +from splunk_ao.resources.types import UNSET, File, Response from splunk_ao.schema.datasets import DatasetRecord @@ -1758,3 +1760,31 @@ def test_get_content_remaps_output_to_ground_truth(get_content_mock: Mock) -> No assert "ground_truth" in row_values assert "output" not in row_values assert row_values["ground_truth"] == "Europe" + + +def test_create_dataset_body_serializes_file_as_multipart_upload() -> None: + # Given: a dataset body constructed with a File payload + file = File(payload=Mock(), file_name="dataset.jsonl", mime_type="application/octet-stream") + body = BodyCreateDatasetDatasetsPost(file=file, name="dataset.jsonl") + + # When: serializing the body to multipart form data + multipart_data = dict(body.to_multipart()) + + # Then: the file field is emitted as a binary multipart upload + assert multipart_data["file"] == file.to_tuple() + + +def test_code_scorer_bodies_serialize_files_as_multipart_uploads() -> None: + # Given: generated code-scorer bodies constructed with File payloads + file = File(payload=Mock(), file_name="scorer.py", mime_type="text/x-python") + bodies = [ + BodyCreateCodeScorerVersionScorersScorerIdVersionCodePost(file=file, validation_result="{}"), + BodyValidateCodeScorerScorersCodeValidatePost(file=file), + ] + + for body in bodies: + # When: serializing the body to multipart form data + multipart_data = dict(body.to_multipart()) + + # Then: the file field is emitted as a binary multipart upload + assert multipart_data["file"] == file.to_tuple() From 5f89e46bb7701b0413aa568ae3a232224bc0f4d0 Mon Sep 17 00:00:00 2001 From: etserend Date: Mon, 20 Jul 2026 18:02:27 -0500 Subject: [PATCH 2/3] fix(scripts): add missing ListAnnotationQueueParams patch and verification block Two gaps vs upstream 02ce8bb: - ListAnnotationQueueParams.properties.sort.default patch was missing (added in e34a5b1 / PR#103 but not carried forward to this branch) - Post-patch verification block (yq -e with 14 assertions) was absent - Error message corrected: "Failed to fetch..." -> "Failed to patch..." --- scripts/import-openapi-yaml.sh | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/scripts/import-openapi-yaml.sh b/scripts/import-openapi-yaml.sh index 4aa25af2..b1f0bb77 100755 --- a/scripts/import-openapi-yaml.sh +++ b/scripts/import-openapi-yaml.sh @@ -32,6 +32,7 @@ curl -s "${HOST_URL}/openapi.json" | poetry run python -c 'import sys, json, yam # - ListDatasetParams.properties.sort.default = "None" # - ListPromptTemplateParams.properties.sort.default = "None" # - ProjectCollectionParams.properties.sort.default = "None" +# - ListAnnotationQueueParams.properties.sort.default = "None" # - galileo_core__schemas__shared__scorers__scorer_name__ScorerName.title = "CoreScorerName" # - galileo_core__schemas__shared__scorers__scorer_name__ScorerName.enum |= unique # - /llm_integrations/projects/{project_id}/runs/{run_id}.get.responses[200].schema.title = "GetRunIntegrationsResponse" (Windows filename length fix) @@ -48,10 +49,34 @@ curl -s "${HOST_URL}/openapi.json" | poetry run python -c 'import sys, json, yam # If you run into related issues with the auto-generate-api-client.sh script, add the openapi.yaml patches here. # Apply all patches using yq in a single command -poetry run python -m yq --in-place -Y '.components.schemas.api__schemas__project_v2__GetProjectsPaginatedResponse.title = "GetProjectsPaginatedResponseV2" | .components.schemas.galileo_core__schemas__shared__message__Message.title = "MessagesListItem" | .components.schemas.galileo_core__schemas__shared__message_role__MessageRole.title = "MessagesListItemRole" | .components.schemas.ListDatasetParams.properties.sort.default = "None" | .components.schemas.ListPromptTemplateParams.properties.sort.default = "None" | .components.schemas.ProjectCollectionParams.properties.sort.default = "None" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.title = "CoreScorerName" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.enum |= unique | .paths["/llm_integrations/projects/{project_id}/runs/{run_id}"].get.responses["200"].content["application/json"].schema.title = "GetRunIntegrationsResponse" | .components.schemas.Document.properties.content = .components.schemas.Document.properties.page_content | del(.components.schemas.Document.properties.page_content) | .components.schemas.Document.properties.content.title = "Content" | .components.schemas.Document.required = ["content"] | .components.schemas["AndNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "AndNodeLogRecordsFilter" | .components.schemas["FilterExpression_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterExpressionLogRecordsFilter" | .components.schemas["FilterLeaf_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterLeafLogRecordsFilter" | .components.schemas["NotNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "NotNodeLogRecordsFilter" | .components.schemas["OrNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "OrNodeLogRecordsFilter" | .components.schemas.StepType.enum += ["control"] | .components.schemas.StepType.enum |= unique | .components.schemas["Body_create_code_scorer_version_scorers__scorer_id__version_code_post"].properties.file.format = "binary" | del(.components.schemas["Body_create_code_scorer_version_scorers__scorer_id__version_code_post"].properties.file.contentMediaType) | .components.schemas["Body_create_dataset_datasets_post"].properties.file.anyOf[0].format = "binary" | del(.components.schemas["Body_create_dataset_datasets_post"].properties.file.anyOf[0].contentMediaType) | .components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.query_files.items.format = "binary" | del(.components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.query_files.items.contentMediaType) | .components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.response_files.items.format = "binary" | del(.components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.response_files.items.contentMediaType) | .components.schemas["Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post"].properties.file.contentMediaType) | .components.schemas["Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post"].properties.file.contentMediaType) | .components.schemas["Body_validate_code_scorer_scorers_code_validate_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_scorers_code_validate_post"].properties.file.contentMediaType)' "$HOME_DIR/openapi.yaml" +poetry run python -m yq --in-place -Y '.components.schemas.api__schemas__project_v2__GetProjectsPaginatedResponse.title = "GetProjectsPaginatedResponseV2" | .components.schemas.galileo_core__schemas__shared__message__Message.title = "MessagesListItem" | .components.schemas.galileo_core__schemas__shared__message_role__MessageRole.title = "MessagesListItemRole" | .components.schemas.ListDatasetParams.properties.sort.default = "None" | .components.schemas.ListPromptTemplateParams.properties.sort.default = "None" | .components.schemas.ProjectCollectionParams.properties.sort.default = "None" | .components.schemas.ListAnnotationQueueParams.properties.sort.default = "None" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.title = "CoreScorerName" | .components.schemas.galileo_core__schemas__shared__scorers__scorer_name__ScorerName.enum |= unique | .paths["/llm_integrations/projects/{project_id}/runs/{run_id}"].get.responses["200"].content["application/json"].schema.title = "GetRunIntegrationsResponse" | .components.schemas.Document.properties.content = .components.schemas.Document.properties.page_content | del(.components.schemas.Document.properties.page_content) | .components.schemas.Document.properties.content.title = "Content" | .components.schemas.Document.required = ["content"] | .components.schemas["AndNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "AndNodeLogRecordsFilter" | .components.schemas["FilterExpression_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterExpressionLogRecordsFilter" | .components.schemas["FilterLeaf_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "FilterLeafLogRecordsFilter" | .components.schemas["NotNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "NotNodeLogRecordsFilter" | .components.schemas["OrNode_Annotated_Union_LogRecordsIDFilter__LogRecordsDateFilter__LogRecordsNumberFilter__LogRecordsBooleanFilter__LogRecordsCollectionFilter__LogRecordsTextFilter__LogRecordsFullyAnnotatedFilter___FieldInfo_annotation_NoneType__required_True__discriminator__type____"].title = "OrNodeLogRecordsFilter" | .components.schemas.StepType.enum += ["control"] | .components.schemas.StepType.enum |= unique | .components.schemas["Body_create_code_scorer_version_scorers__scorer_id__version_code_post"].properties.file.format = "binary" | del(.components.schemas["Body_create_code_scorer_version_scorers__scorer_id__version_code_post"].properties.file.contentMediaType) | .components.schemas["Body_create_dataset_datasets_post"].properties.file.anyOf[0].format = "binary" | del(.components.schemas["Body_create_dataset_datasets_post"].properties.file.anyOf[0].contentMediaType) | .components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.query_files.items.format = "binary" | del(.components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.query_files.items.contentMediaType) | .components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.response_files.items.format = "binary" | del(.components.schemas["Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post"].properties.response_files.items.contentMediaType) | .components.schemas["Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post"].properties.file.contentMediaType) | .components.schemas["Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post"].properties.file.contentMediaType) | .components.schemas["Body_validate_code_scorer_scorers_code_validate_post"].properties.file.format = "binary" | del(.components.schemas["Body_validate_code_scorer_scorers_code_validate_post"].properties.file.contentMediaType)' "$HOME_DIR/openapi.yaml" if [ $? -ne 0 ]; then - echo "Failed to fetch and convert OpenAPI JSON" + echo "Failed to patch OpenAPI YAML" + exit 1 +fi + +poetry run python -m yq -e ' + [ + .components.schemas.Body_create_code_scorer_version_scorers__scorer_id__version_code_post.properties.file.format == "binary", + (.components.schemas.Body_create_code_scorer_version_scorers__scorer_id__version_code_post.properties.file | has("contentMediaType") | not), + .components.schemas.Body_create_dataset_datasets_post.properties.file.anyOf[0].format == "binary", + (.components.schemas.Body_create_dataset_datasets_post.properties.file.anyOf[0] | has("contentMediaType") | not), + .components.schemas.Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.properties.query_files.items.format == "binary", + (.components.schemas.Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.properties.query_files.items | has("contentMediaType") | not), + .components.schemas.Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.properties.response_files.items.format == "binary", + (.components.schemas.Body_manual_llm_validate_multipart_scorers_llm_validate_multipart_post.properties.response_files.items | has("contentMediaType") | not), + .components.schemas.Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post.properties.file.format == "binary", + (.components.schemas.Body_validate_code_scorer_dataset_scorers_code_validate_dataset_post.properties.file | has("contentMediaType") | not), + .components.schemas.Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post.properties.file.format == "binary", + (.components.schemas.Body_validate_code_scorer_log_record_scorers_code_validate_log_record_post.properties.file | has("contentMediaType") | not), + .components.schemas.Body_validate_code_scorer_scorers_code_validate_post.properties.file.format == "binary", + (.components.schemas.Body_validate_code_scorer_scorers_code_validate_post.properties.file | has("contentMediaType") | not) + ] | all +' "$HOME_DIR/openapi.yaml" > /dev/null + +if [ $? -ne 0 ]; then + echo "Failed to patch multipart binary fields in OpenAPI YAML" exit 1 fi From b2f1555ff94a60a68bf52e2bfb47d82e7a5902a7 Mon Sep 17 00:00:00 2001 From: etserend Date: Wed, 22 Jul 2026 11:54:53 -0500 Subject: [PATCH 3/3] fix(tests): relax duration assertion to allow 0ns on low-resolution clocks On Windows, time.time_ns() resolution can be coarser than the execution time of a trivial function, so duration_ns can legitimately be 0. Co-Authored-By: Claude Opus 4.7 --- tests/test_decorator_distributed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_decorator_distributed.py b/tests/test_decorator_distributed.py index a14baaf4..97f262d5 100644 --- a/tests/test_decorator_distributed.py +++ b/tests/test_decorator_distributed.py @@ -522,7 +522,7 @@ def workflow_step_2() -> str: # Verify duration is set and is a reasonable value (>= 0) assert first_duration is not None, "First trace duration should be set" - assert first_duration > 0, f"First trace duration should be >= 0, got {first_duration}ns" + assert first_duration >= 0, f"First trace duration should be >= 0, got {first_duration}ns" # Execute second workflow result2 = workflow_step_2()