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..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) @@ -43,16 +44,40 @@ 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.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" -# Check if the command was successful -if [ $? -eq 0 ]; then - echo "OpenAPI YAML saved to $HOME_DIR/openapi.yaml" -else - echo "Failed to fetch and convert OpenAPI JSON" +if [ $? -ne 0 ]; then + 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 + +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 e2318407..d3c1ef54 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,12 +1,14 @@ from __future__ import annotations 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") @@ -14,17 +16,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 @@ -37,7 +40,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"))) @@ -49,7 +52,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 2f330d80..09d6fa3a 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,13 +1,14 @@ from __future__ import annotations from collections.abc import Mapping +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") @@ -20,7 +21,7 @@ class BodyCreateDatasetDatasetsPost: hidden (bool | Unset): Default: False. name (None | str | Unset): append_suffix_if_duplicate (bool | Unset): Default: False. - file (None | str | Unset): + file (File | None | Unset): copy_from_dataset_id (None | str | Unset): copy_from_dataset_version_index (int | None | Unset): project_id (None | str | Unset): @@ -31,7 +32,7 @@ class BodyCreateDatasetDatasetsPost: hidden: bool | Unset = False name: None | str | Unset = UNSET append_suffix_if_duplicate: bool | Unset = False - file: None | str | Unset = UNSET + file: File | None | Unset = UNSET copy_from_dataset_id: None | str | Unset = UNSET copy_from_dataset_version_index: int | None | Unset = UNSET project_id: None | str | Unset = UNSET @@ -51,9 +52,11 @@ def to_dict(self) -> dict[str, Any]: append_suffix_if_duplicate = self.append_suffix_if_duplicate - file: None | str | Unset + 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 @@ -126,8 +129,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"))) @@ -188,12 +191,18 @@ def _parse_name(data: object) -> None | str | Unset: append_suffix_if_duplicate = d.pop("append_suffix_if_duplicate", UNSET) - def _parse_file(data: object) -> None | str | Unset: + def _parse_file(data: object) -> File | None | Unset: if data is None: return data if isinstance(data, Unset): return data - return cast(None | str | Unset, 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)) 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 712eedb9..c2a51740 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,13 +1,14 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any, TypeVar, 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, Unset T = TypeVar("T", bound="BodyManualLlmValidateMultipartScorersLlmValidateMultipartPost") @@ -17,25 +18,31 @@ class BodyManualLlmValidateMultipartScorersLlmValidateMultipartPost: """ Attributes: body (str): JSON-encoded GeneratedScorerValidationRequest - query_files (list[str] | Unset): - response_files (list[str] | Unset): + query_files (list[File] | Unset): + response_files (list[File] | Unset): """ body: str - query_files: list[str] | Unset = UNSET - response_files: list[str] | Unset = UNSET + query_files: list[File] | Unset = UNSET + response_files: list[File] | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: body = self.body - query_files: list[str] | Unset = UNSET + query_files: list[Any] | Unset = 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: list[str] | Unset = UNSET + response_files: list[Any] | Unset = 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) @@ -54,11 +61,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"))) @@ -70,9 +77,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 6260449b..5ae6a940 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,6 +1,7 @@ from __future__ import annotations from collections.abc import Mapping +from io import BytesIO from typing import Any, TypeVar, cast from uuid import UUID @@ -8,7 +9,7 @@ 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") @@ -17,7 +18,7 @@ class BodyValidateCodeScorerDatasetScorersCodeValidateDatasetPost: """ Attributes: - file (str): + file (File): dataset_id (UUID): dataset_version_index (int | None | Unset): limit (int | Unset): Default: 100. @@ -27,7 +28,7 @@ class BodyValidateCodeScorerDatasetScorersCodeValidateDatasetPost: score_type (None | str | Unset): """ - file: str + file: File dataset_id: UUID dataset_version_index: int | None | Unset = UNSET limit: int | Unset = 100 @@ -38,7 +39,7 @@ class BodyValidateCodeScorerDatasetScorersCodeValidateDatasetPost: 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) @@ -101,7 +102,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"))) @@ -159,7 +160,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"))) dataset_id = UUID(d.pop("dataset_id")) @@ -191,9 +192,8 @@ def _parse_required_scorers(data: object) -> list[str] | None | str | Unset: 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(list[str] | None | str | Unset, data) @@ -208,9 +208,8 @@ def _parse_scoreable_node_types(data: object) -> list[str] | None | str | Unset: 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(list[str] | None | str | Unset, data) 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 4915a208..3a39a97a 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,13 +1,14 @@ from __future__ import annotations from collections.abc import Mapping +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") @@ -16,7 +17,7 @@ class BodyValidateCodeScorerLogRecordScorersCodeValidateLogRecordPost: """ Attributes: - file (str): + file (File): log_stream_id (None | str | Unset): experiment_id (None | str | Unset): limit (int | Unset): Default: 100. @@ -27,7 +28,7 @@ class BodyValidateCodeScorerLogRecordScorersCodeValidateLogRecordPost: scoreable_node_types (list[str] | None | str | Unset): """ - file: str + file: File log_stream_id: None | str | Unset = UNSET experiment_id: None | str | Unset = UNSET limit: int | Unset = 100 @@ -39,7 +40,7 @@ class BodyValidateCodeScorerLogRecordScorersCodeValidateLogRecordPost: 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: None | str | Unset if isinstance(self.log_stream_id, Unset): @@ -116,7 +117,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): @@ -184,7 +185,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"))) def _parse_log_stream_id(data: object) -> None | str | Unset: if data is None: @@ -241,9 +242,8 @@ def _parse_required_scorers(data: object) -> list[str] | None | str | Unset: 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(list[str] | None | str | Unset, data) @@ -258,9 +258,8 @@ def _parse_scoreable_node_types(data: object) -> list[str] | None | str | Unset: 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(list[str] | None | str | Unset, data) 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 697f9cd1..44c2f761 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,13 +1,14 @@ from __future__ import annotations from collections.abc import Mapping +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") @@ -16,14 +17,14 @@ class BodyValidateCodeScorerScorersCodeValidatePost: """ Attributes: - file (str): + file (File): test_input (None | str | Unset): test_output (None | str | Unset): required_scorers (list[str] | None | str | Unset): scoreable_node_types (list[str] | None | str | Unset): """ - file: str + file: File test_input: None | str | Unset = UNSET test_output: None | str | Unset = UNSET required_scorers: list[str] | None | str | Unset = UNSET @@ -31,7 +32,7 @@ class BodyValidateCodeScorerScorersCodeValidatePost: 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: None | str | Unset if isinstance(self.test_input, Unset): @@ -80,7 +81,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): @@ -127,7 +128,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"))) def _parse_test_input(data: object) -> None | str | Unset: if data is None: 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() 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()