From 11562d1437af5ef441c5402634f344b5cf448097 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 14 Sep 2026 12:55:10 -0400 Subject: [PATCH 1/2] feat(api): generate remaining reviewed REST resources Publish 20 additional OpenAPI tags through BraintrustOpenApiClient and export their reviewed request and response types. Keep streaming, ingestion, diagnostic, CORS, and Proxy tags explicitly unsupported with documented reasons. Require every pinned spec tag to be generated or intentionally excluded, and validate malformed tag containers with actionable errors. Extend parameter generation for the metadata-wrapped allOf shape used by project scores. Refs #683 --- openapi/README.md | 33 +- openapi/config.json | 30 +- py/scripts/openapi_codegen.py | 71 +- py/src/braintrust/api/_generated/acls.py | 419 ++++++ py/src/braintrust/api/_generated/agents.py | 250 ++++ .../braintrust/api/_generated/ai_secrets.py | 293 ++++ py/src/braintrust/api/_generated/api_keys.py | 163 +++ .../api/_generated/dataset_snapshots.py | 257 ++++ py/src/braintrust/api/_generated/env_vars.py | 244 ++++ .../braintrust/api/_generated/environments.py | 198 +++ py/src/braintrust/api/_generated/groups.py | 250 ++++ .../braintrust/api/_generated/mcp_servers.py | 257 ++++ .../api/_generated/models/__init__.py | 510 ++++++- .../braintrust/api/_generated/models/acls.py | 184 +++ .../api/_generated/models/agents.py | 99 ++ .../api/_generated/models/ai_secrets.py | 103 ++ .../api/_generated/models/api_keys.py | 69 + .../api/_generated/models/common.py | 107 +- .../_generated/models/dataset_snapshots.py | 83 ++ .../api/_generated/models/datasets.py | 4 +- .../api/_generated/models/env_vars.py | 90 ++ .../api/_generated/models/environments.py | 80 ++ .../api/_generated/models/experiments.py | 4 +- .../api/_generated/models/functions.py | 33 +- .../api/_generated/models/groups.py | 124 ++ .../api/_generated/models/mcp_servers.py | 98 ++ .../api/_generated/models/org_automations.py | 107 ++ .../api/_generated/models/organizations.py | 163 +++ .../_generated/models/project_automations.py | 1209 +++++++++++++++++ .../api/_generated/models/project_groups.py | 100 ++ .../api/_generated/models/project_scores.py | 208 +++ .../api/_generated/models/project_tags.py | 95 ++ .../api/_generated/models/projects.py | 4 +- .../api/_generated/models/prompts.py | 4 +- .../braintrust/api/_generated/models/roles.py | 143 ++ .../api/_generated/models/service_tokens.py | 112 ++ .../api/_generated/models/span_iframes.py | 110 ++ .../braintrust/api/_generated/models/users.py | 66 + .../braintrust/api/_generated/models/views.py | 335 +++++ .../api/_generated/org_automations.py | 257 ++++ .../api/_generated/organizations.py | 191 +++ .../api/_generated/project_automations.py | 257 ++++ .../api/_generated/project_groups.py | 257 ++++ .../api/_generated/project_scores.py | 284 ++++ .../braintrust/api/_generated/project_tags.py | 273 ++++ py/src/braintrust/api/_generated/roles.py | 250 ++++ .../api/_generated/service_tokens.py | 250 ++++ .../braintrust/api/_generated/span_iframes.py | 257 ++++ py/src/braintrust/api/_generated/users.py | 147 ++ py/src/braintrust/api/_generated/views.py | 284 ++++ py/src/braintrust/api/client.py | 51 +- .../braintrust/api/test_generated_models.py | 80 +- py/src/braintrust/api/types/__init__.py | 158 +++ .../braintrust/type_tests/test_api_client.py | 22 + py/tests/api_codegen/conftest.py | 1 + py/tests/api_codegen/test_generation.py | 68 + py/tests/api_codegen/test_validation.py | 91 +- 57 files changed, 9822 insertions(+), 65 deletions(-) create mode 100644 py/src/braintrust/api/_generated/acls.py create mode 100644 py/src/braintrust/api/_generated/agents.py create mode 100644 py/src/braintrust/api/_generated/ai_secrets.py create mode 100644 py/src/braintrust/api/_generated/api_keys.py create mode 100644 py/src/braintrust/api/_generated/dataset_snapshots.py create mode 100644 py/src/braintrust/api/_generated/env_vars.py create mode 100644 py/src/braintrust/api/_generated/environments.py create mode 100644 py/src/braintrust/api/_generated/groups.py create mode 100644 py/src/braintrust/api/_generated/mcp_servers.py create mode 100644 py/src/braintrust/api/_generated/models/acls.py create mode 100644 py/src/braintrust/api/_generated/models/agents.py create mode 100644 py/src/braintrust/api/_generated/models/ai_secrets.py create mode 100644 py/src/braintrust/api/_generated/models/api_keys.py create mode 100644 py/src/braintrust/api/_generated/models/dataset_snapshots.py create mode 100644 py/src/braintrust/api/_generated/models/env_vars.py create mode 100644 py/src/braintrust/api/_generated/models/environments.py create mode 100644 py/src/braintrust/api/_generated/models/groups.py create mode 100644 py/src/braintrust/api/_generated/models/mcp_servers.py create mode 100644 py/src/braintrust/api/_generated/models/org_automations.py create mode 100644 py/src/braintrust/api/_generated/models/organizations.py create mode 100644 py/src/braintrust/api/_generated/models/project_automations.py create mode 100644 py/src/braintrust/api/_generated/models/project_groups.py create mode 100644 py/src/braintrust/api/_generated/models/project_scores.py create mode 100644 py/src/braintrust/api/_generated/models/project_tags.py create mode 100644 py/src/braintrust/api/_generated/models/roles.py create mode 100644 py/src/braintrust/api/_generated/models/service_tokens.py create mode 100644 py/src/braintrust/api/_generated/models/span_iframes.py create mode 100644 py/src/braintrust/api/_generated/models/users.py create mode 100644 py/src/braintrust/api/_generated/models/views.py create mode 100644 py/src/braintrust/api/_generated/org_automations.py create mode 100644 py/src/braintrust/api/_generated/organizations.py create mode 100644 py/src/braintrust/api/_generated/project_automations.py create mode 100644 py/src/braintrust/api/_generated/project_groups.py create mode 100644 py/src/braintrust/api/_generated/project_scores.py create mode 100644 py/src/braintrust/api/_generated/project_tags.py create mode 100644 py/src/braintrust/api/_generated/roles.py create mode 100644 py/src/braintrust/api/_generated/service_tokens.py create mode 100644 py/src/braintrust/api/_generated/span_iframes.py create mode 100644 py/src/braintrust/api/_generated/users.py create mode 100644 py/src/braintrust/api/_generated/views.py diff --git a/openapi/README.md b/openapi/README.md index fae49363..3cb30087 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -16,9 +16,36 @@ make check-api-client-codegen ``` The check regenerates in a temporary directory and reports drift without changing the worktree. -Currently selected tags are Projects, Experiments, Datasets, Prompts, and Functions. Each tag produces one resource -and operation registry. Models used by one resource stay in that resource's model module; shared models -live in `models/common.py`; unreachable models are omitted. +The reviewed generated surface includes these tags: + +- core resources: Projects, Experiments, Datasets, Prompts, and Functions; +- access and organization resources: Acls, Groups, ProjectGroups, Roles, Users, Organizations, + ApiKeys, and ServiceTokens; +- configuration resources: AiSecrets, EnvVars, Environments, and McpServers; +- project resources: Agents, ProjectAutomations, OrgAutomations, ProjectScores, ProjectTags, + SpanIframes, and Views; and +- versioned data resources: DatasetSnapshots. + +Each tag produces one resource and operation registry. Models used by one resource stay in that +resource's model module; shared models live in `models/common.py`; unreachable models are omitted. + +Every tag in the pinned spec must be either selected or present in `unsupported_tags` in +`config.json` with a rationale. The intentionally unsupported tags are: + +- **CORS:** browser preflight `OPTIONS` operations are transport concerns rather than callable + resource methods. +- **CrossObject:** cross-object event insertion belongs to the specialized at-least-once + log-ingestion path. +- **Evals:** eval launch is a long-running, payload-dependent workflow that can stream and needs a + specialized client. +- **Logs:** project-log event ingestion, fetching, and feedback remain on the specialized logging + path. +- **Other:** the unauthenticated, text-only hello-world endpoint is a service diagnostic rather + than a public REST resource. +- **Proxy:** provider passthrough needs proxy-target routing, streaming, and provider-specific + response behavior. Its catch-all `proxy{path+}` operation ID is also not a valid Python + identifier. Proxy remains on specialized SDK paths and is deliberately absent from generated + modules and `BraintrustOpenApiClient`. Method and inline-response names come directly from normalized OpenAPI `operationId` values. Generated models preserve exact wire keys, including leading underscores, and methods do not add implicit request diff --git a/openapi/config.json b/openapi/config.json index 49815ceb..40f821ea 100644 --- a/openapi/config.json +++ b/openapi/config.json @@ -34,8 +34,36 @@ "Experiments", "Datasets", "Prompts", - "Functions" + "Functions", + "Acls", + "Agents", + "AiSecrets", + "ApiKeys", + "DatasetSnapshots", + "EnvVars", + "Environments", + "Groups", + "McpServers", + "OrgAutomations", + "Organizations", + "ProjectAutomations", + "ProjectGroups", + "ProjectScores", + "ProjectTags", + "Roles", + "ServiceTokens", + "SpanIframes", + "Users", + "Views" ], + "unsupported_tags": { + "CORS": "Browser preflight OPTIONS operations are transport concerns, not callable resource methods.", + "CrossObject": "Cross-object event insertion belongs to the specialized at-least-once log-ingestion path.", + "Evals": "Eval launch is a long-running, payload-dependent workflow that can stream and requires a specialized client.", + "Logs": "Project-log event ingestion, fetching, and feedback remain on the specialized logging path.", + "Other": "The unauthenticated text-only hello-world endpoint is a service diagnostic, not a public resource.", + "Proxy": "Provider passthrough requires proxy routing and streaming support, and the catch-all operationId is not valid Python." + }, "specialized_operations": [ "postFunctionIdInvoke" ], diff --git a/py/scripts/openapi_codegen.py b/py/scripts/openapi_codegen.py index e3df2c84..9d3cb744 100644 --- a/py/scripts/openapi_codegen.py +++ b/py/scripts/openapi_codegen.py @@ -152,6 +152,7 @@ def validate_spec(spec: Mapping[str, Any], config: Mapping[str, Any]) -> Validat endpoint = _endpoint_config(config) all_operations = list(_iter_operations(spec)) _validate_unique_operation_ids(all_operations) + _validate_reviewed_tags(all_operations, endpoint) _validate_specialized_operations(all_operations, endpoint) operations = _selected_operations( all_operations, @@ -170,9 +171,6 @@ def validate_spec(spec: Mapping[str, Any], config: Mapping[str, Any]) -> Validat for method, path, operation_id, operation, path_item in operations: if not operation_id or not OPERATION_ID_RE.fullmatch(operation_id): raise CodegenError(f"Operation {method.upper()} {path} has an invalid operationId: {operation_id!r}") - tags = operation.get("tags") - if not isinstance(tags, list) or not tags or not all(isinstance(tag, str) and tag.strip() for tag in tags): - raise CodegenError(f"Operation {operation_id!r} must have usable tags") if len(_generated_operation_tags(operation, endpoint["generated_tags"])) != 1: raise CodegenError(f"Operation {operation_id!r} must have exactly one generated OpenAPI tag") generated_name = _python_type_name(operation_id) @@ -203,12 +201,18 @@ def validate_spec(spec: Mapping[str, Any], config: Mapping[str, Any]) -> Validat return ValidationReport(len(operations), len(schemas)) -def _generated_operation_tags(operation: Mapping[str, Any], generated_tags: Sequence[str]) -> List[str]: - tags = operation.get("tags") - if not isinstance(tags, list): +def _operation_tags(operation: Mapping[str, Any]) -> List[str]: + if "tags" not in operation: return [] + tags = operation["tags"] + if not isinstance(tags, list) or not tags or not all(isinstance(tag, str) and tag.strip() for tag in tags): + raise CodegenError(f"Operation {operation.get('operationId')!r} tags must be a list of non-empty strings") + return tags + + +def _generated_operation_tags(operation: Mapping[str, Any], generated_tags: Sequence[str]) -> List[str]: selected_tags = set(generated_tags) - return [tag for tag in tags if isinstance(tag, str) and tag in selected_tags] + return [tag for tag in _operation_tags(operation) if tag in selected_tags] def _selected_operations( @@ -718,6 +722,24 @@ def _model_package_source(model_modules: Mapping[str, str]) -> str: return "\n".join(lines) +def _validate_reviewed_tags( + operations: Sequence[Tuple[str, str, Any, Mapping[str, Any], Mapping[str, Any]]], + endpoint: Mapping[str, Any], +) -> None: + spec_tags = {tag for _, _, _, operation, _ in operations for tag in _operation_tags(operation)} + generated_tags = set(endpoint["generated_tags"]) + unsupported_tags = set(endpoint["unsupported_tags"]) + overlap = generated_tags & unsupported_tags + if overlap: + raise CodegenError(f"OpenAPI tags cannot be both generated and unsupported: {sorted(overlap)}") + unreviewed = spec_tags - generated_tags - unsupported_tags + if unreviewed: + raise CodegenError(f"OpenAPI spec contains unreviewed OpenAPI tags: {sorted(unreviewed)}") + stale = unsupported_tags - spec_tags + if stale: + raise CodegenError(f"endpoint_generator.unsupported_tags contains unknown tags: {sorted(stale)}") + + def _validate_specialized_operations( operations: Sequence[Tuple[str, str, Any, Mapping[str, Any], Mapping[str, Any]]], endpoint: Mapping[str, Any], @@ -923,7 +945,7 @@ def _schema_annotation(schema: Mapping[str, Any], spec: Mapping[str, Any]) -> st choices = resolved.get("oneOf", resolved.get("anyOf", [])) annotation = " | ".join(_schema_annotation(choice, spec) for choice in choices) or "Any" elif "allOf" in resolved: - choices = resolved["allOf"] + choices = _structural_all_of_choices(resolved["allOf"]) annotation = _schema_annotation(choices[0], spec) if len(choices) == 1 else "Any" else: schema_type = resolved.get("type") @@ -1135,6 +1157,12 @@ def _endpoint_config(config: Mapping[str, Any]) -> Mapping[str, Any]: or len(values) != len(set(values)) ): raise CodegenError(f"endpoint_generator.{key} must be a unique list of non-empty strings") + unsupported_tags = endpoint.get("unsupported_tags") + if not isinstance(unsupported_tags, dict) or not all( + isinstance(tag, str) and tag and isinstance(reason, str) and reason.strip() + for tag, reason in unsupported_tags.items() + ): + raise CodegenError("endpoint_generator.unsupported_tags must map tag names to non-empty reasons") for key in ("supported_request_media_types", "supported_response_media_types", "supported_success_statuses"): values = endpoint.get(key) if not isinstance(values, list) or not values or not all(isinstance(value, str) for value in values): @@ -1302,6 +1330,15 @@ def _validate_parameters( def _parameter_schema_kinds(schema: Mapping[str, Any], spec: Mapping[str, Any]) -> Set[str]: schema = _resolve_object(schema, spec) + all_of = schema.get("allOf") + if isinstance(all_of, list): + kinds: Set[str] = set() + for choice in _structural_all_of_choices(all_of): + kinds.update(_parameter_schema_kinds(choice, spec)) + if kinds: + return kinds + raise CodegenError("Query parameter allOf must contain a structural schema") + choices = schema.get("oneOf", schema.get("anyOf")) if isinstance(choices, list): kinds: Set[str] = set() @@ -1322,6 +1359,24 @@ def _parameter_schema_kinds(schema: Mapping[str, Any], spec: Mapping[str, Any]) raise CodegenError(f"Unsupported query parameter schema type {schema_type!r}") +def _structural_all_of_choices(choices: Sequence[Any]) -> List[Mapping[str, Any]]: + structural_keywords = { + "$ref", + "allOf", + "anyOf", + "enum", + "items", + "oneOf", + "properties", + "type", + } + return [ + choice + for choice in choices + if isinstance(choice, dict) and any(keyword in choice for keyword in structural_keywords) + ] + + def _validate_component_names(schemas: Mapping[str, Any]) -> None: names: Dict[str, str] = {} for schema_name in schemas: diff --git a/py/src/braintrust/api/_generated/acls.py b/py/src/braintrust/api/_generated/acls.py new file mode 100644 index 00000000..8744e9d6 --- /dev/null +++ b/py/src/braintrust/api/_generated/acls.py @@ -0,0 +1,419 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: f6e047cca4248110070e62fa23535ca244f4a8136afe651d25c9f0ff94f687e1 + +"""Generated Acls REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.acls import ( + Acl, + AclBatchUpdateRequest, + AclBatchUpdateResponse, + AclIdParam, + AclItem, + AclListGroupId, + AclListOrgObjectId, + AclListOrgObjectType, + AclListOrgResponse, + AclListPermission, + AclListRestrictObjectType, + AclListRoleId, + AclListUserId, + GetAclResponse, +) +from .models.common import AclObjectId, AclObjectType, AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter + + +POST_ACL = Operation( + operation_id="postAcl", + method="POST", + path="/v1/acl", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_ACL = Operation( + operation_id="deleteAcl", + method="DELETE", + path="/v1/acl", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_ACL = Operation( + operation_id="getAcl", + method="GET", + path="/v1/acl", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="object_type", + name="object_type", + location="query", + required=True, + ), + Parameter( + argument_name="object_id", + name="object_id", + location="query", + required=True, + ), + Parameter( + argument_name="user_id", + name="user_id", + location="query", + required=False, + ), + Parameter( + argument_name="group_id", + name="group_id", + location="query", + required=False, + ), + Parameter( + argument_name="permission", + name="permission", + location="query", + required=False, + ), + Parameter( + argument_name="restrict_object_type", + name="restrict_object_type", + location="query", + required=False, + ), + Parameter( + argument_name="role_id", + name="role_id", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_ACL_ID = Operation( + operation_id="getAclId", + method="GET", + path="/v1/acl/{acl_id}", + parameters=( + Parameter( + argument_name="acl_id", + name="acl_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +DELETE_ACL_ID = Operation( + operation_id="deleteAclId", + method="DELETE", + path="/v1/acl/{acl_id}", + parameters=( + Parameter( + argument_name="acl_id", + name="acl_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +ACL_BATCH_UPDATE = Operation( + operation_id="aclBatchUpdate", + method="POST", + path="/v1/acl/batch_update", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +ACL_LIST_ORG = Operation( + operation_id="aclListOrg", + method="GET", + path="/v1/acl/list_org", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="object_type", + name="object_type", + location="query", + required=False, + ), + Parameter( + argument_name="object_id", + name="object_id", + location="query", + required=False, + ), + Parameter( + argument_name="user_id", + name="user_id", + location="query", + required=False, + ), + Parameter( + argument_name="group_id", + name="group_id", + location="query", + required=False, + ), + Parameter( + argument_name="permission", + name="permission", + location="query", + required=False, + ), + Parameter( + argument_name="restrict_object_type", + name="restrict_object_type", + location="query", + required=False, + ), + Parameter( + argument_name="role_id", + name="role_id", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +OPERATIONS = { + "postAcl": POST_ACL, + "deleteAcl": DELETE_ACL, + "getAcl": GET_ACL, + "getAclId": GET_ACL_ID, + "deleteAclId": DELETE_ACL_ID, + "aclBatchUpdate": ACL_BATCH_UPDATE, + "aclListOrg": ACL_LIST_ORG, +} + + +class AclsAPI(ResourceAPI): + """Generated Acls REST API.""" + + def post_acl( + self, + *, + body: "AclItem", + ) -> "Acl": + return cast( + "Acl", + self.execute( + POST_ACL, + body=body, + ), + ) + + def delete_acl( + self, + *, + body: "AclItem", + ) -> "Acl": + return cast( + "Acl", + self.execute( + DELETE_ACL, + body=body, + ), + ) + + def get_acl( + self, + object_type: "AclObjectType", + object_id: "AclObjectId", + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + user_id: "AclListUserId | None" = None, + group_id: "AclListGroupId | None" = None, + permission: "AclListPermission | None" = None, + restrict_object_type: "AclListRestrictObjectType | None" = None, + role_id: "AclListRoleId | None" = None, + ) -> "GetAclResponse": + return cast( + "GetAclResponse", + self.execute( + GET_ACL, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "object_type": object_type, + "object_id": object_id, + "user_id": user_id, + "group_id": group_id, + "permission": permission, + "restrict_object_type": restrict_object_type, + "role_id": role_id, + }, + ), + ) + + def get_acl_id( + self, + acl_id: "AclIdParam", + ) -> "Acl": + return cast( + "Acl", + self.execute( + GET_ACL_ID, + path_parameters={"acl_id": acl_id}, + ), + ) + + def delete_acl_id( + self, + acl_id: "AclIdParam", + ) -> "Acl": + return cast( + "Acl", + self.execute( + DELETE_ACL_ID, + path_parameters={"acl_id": acl_id}, + ), + ) + + def acl_batch_update( + self, + *, + body: "AclBatchUpdateRequest | None" = None, + ) -> "AclBatchUpdateResponse": + return cast( + "AclBatchUpdateResponse", + self.execute( + ACL_BATCH_UPDATE, + body=body, + ), + ) + + def acl_list_org( + self, + *, + limit: "AppLimitParam | None" = None, + ids: "Ids | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + object_type: "AclListOrgObjectType | None" = None, + object_id: "AclListOrgObjectId | None" = None, + user_id: "AclListUserId | None" = None, + group_id: "AclListGroupId | None" = None, + permission: "AclListPermission | None" = None, + restrict_object_type: "AclListRestrictObjectType | None" = None, + role_id: "AclListRoleId | None" = None, + org_name: "OrgName | None" = None, + ) -> "AclListOrgResponse": + return cast( + "AclListOrgResponse", + self.execute( + ACL_LIST_ORG, + query_parameters={ + "limit": limit, + "ids": ids, + "starting_after": starting_after, + "ending_before": ending_before, + "object_type": object_type, + "object_id": object_id, + "user_id": user_id, + "group_id": group_id, + "permission": permission, + "restrict_object_type": restrict_object_type, + "role_id": role_id, + "org_name": org_name, + }, + ), + ) diff --git a/py/src/braintrust/api/_generated/agents.py b/py/src/braintrust/api/_generated/agents.py new file mode 100644 index 00000000..126c6199 --- /dev/null +++ b/py/src/braintrust/api/_generated/agents.py @@ -0,0 +1,250 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 91870cec12a1ea3a24bf9925387834e703dc75dedd95b1fbe127af6aa3fa5d8a + +"""Generated Agents REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.agents import Agent, AgentIdParam, AgentName, CreateAgent, GetAgentResponse, PatchAgent +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter + + +POST_AGENT = Operation( + operation_id="postAgent", + method="POST", + path="/v1/agent", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_AGENT = Operation( + operation_id="putAgent", + method="PUT", + path="/v1/agent", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_AGENT = Operation( + operation_id="getAgent", + method="GET", + path="/v1/agent", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="agent_name", + name="agent_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_AGENT_ID = Operation( + operation_id="getAgentId", + method="GET", + path="/v1/agent/{agent_id}", + parameters=( + Parameter( + argument_name="agent_id", + name="agent_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_AGENT_ID = Operation( + operation_id="patchAgentId", + method="PATCH", + path="/v1/agent/{agent_id}", + parameters=( + Parameter( + argument_name="agent_id", + name="agent_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_AGENT_ID = Operation( + operation_id="deleteAgentId", + method="DELETE", + path="/v1/agent/{agent_id}", + parameters=( + Parameter( + argument_name="agent_id", + name="agent_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postAgent": POST_AGENT, + "putAgent": PUT_AGENT, + "getAgent": GET_AGENT, + "getAgentId": GET_AGENT_ID, + "patchAgentId": PATCH_AGENT_ID, + "deleteAgentId": DELETE_AGENT_ID, +} + + +class AgentsAPI(ResourceAPI): + """Generated Agents REST API.""" + + def post_agent( + self, + *, + body: "CreateAgent", + ) -> "Agent": + return cast( + "Agent", + self.execute( + POST_AGENT, + body=body, + ), + ) + + def put_agent( + self, + *, + body: "CreateAgent", + ) -> "Agent": + return cast( + "Agent", + self.execute( + PUT_AGENT, + body=body, + ), + ) + + def get_agent( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + agent_name: "AgentName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetAgentResponse": + return cast( + "GetAgentResponse", + self.execute( + GET_AGENT, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "agent_name": agent_name, + "org_name": org_name, + }, + ), + ) + + def get_agent_id( + self, + agent_id: "AgentIdParam", + ) -> "Agent": + return cast( + "Agent", + self.execute( + GET_AGENT_ID, + path_parameters={"agent_id": agent_id}, + ), + ) + + def patch_agent_id( + self, + agent_id: "AgentIdParam", + *, + body: "PatchAgent | None" = None, + ) -> "Agent": + return cast( + "Agent", + self.execute( + PATCH_AGENT_ID, + path_parameters={"agent_id": agent_id}, + body=body, + ), + ) + + def delete_agent_id( + self, + agent_id: "AgentIdParam", + ) -> "Agent": + return cast( + "Agent", + self.execute( + DELETE_AGENT_ID, + path_parameters={"agent_id": agent_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/ai_secrets.py b/py/src/braintrust/api/_generated/ai_secrets.py new file mode 100644 index 00000000..b9412a57 --- /dev/null +++ b/py/src/braintrust/api/_generated/ai_secrets.py @@ -0,0 +1,293 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 23a87735501958acd619fa0721760d0b9e7c495142fe6f40909a219df40d564a + +"""Generated AiSecrets REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.ai_secrets import ( + AISecret, + AISecretType, + AiSecretIdParam, + AiSecretName, + CreateAISecret, + DeleteAISecret, + GetAiSecretResponse, + PatchAISecret, +) +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter + + +POST_AI_SECRET = Operation( + operation_id="postAiSecret", + method="POST", + path="/v1/ai_secret", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_AI_SECRET = Operation( + operation_id="putAiSecret", + method="PUT", + path="/v1/ai_secret", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_AI_SECRET = Operation( + operation_id="deleteAiSecret", + method="DELETE", + path="/v1/ai_secret", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_AI_SECRET = Operation( + operation_id="getAiSecret", + method="GET", + path="/v1/ai_secret", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="ai_secret_name", + name="ai_secret_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + Parameter( + argument_name="ai_secret_type", + name="ai_secret_type", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_AI_SECRET_ID = Operation( + operation_id="getAiSecretId", + method="GET", + path="/v1/ai_secret/{ai_secret_id}", + parameters=( + Parameter( + argument_name="ai_secret_id", + name="ai_secret_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_AI_SECRET_ID = Operation( + operation_id="patchAiSecretId", + method="PATCH", + path="/v1/ai_secret/{ai_secret_id}", + parameters=( + Parameter( + argument_name="ai_secret_id", + name="ai_secret_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_AI_SECRET_ID = Operation( + operation_id="deleteAiSecretId", + method="DELETE", + path="/v1/ai_secret/{ai_secret_id}", + parameters=( + Parameter( + argument_name="ai_secret_id", + name="ai_secret_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postAiSecret": POST_AI_SECRET, + "putAiSecret": PUT_AI_SECRET, + "deleteAiSecret": DELETE_AI_SECRET, + "getAiSecret": GET_AI_SECRET, + "getAiSecretId": GET_AI_SECRET_ID, + "patchAiSecretId": PATCH_AI_SECRET_ID, + "deleteAiSecretId": DELETE_AI_SECRET_ID, +} + + +class AiSecretsAPI(ResourceAPI): + """Generated AiSecrets REST API.""" + + def post_ai_secret( + self, + *, + body: "CreateAISecret", + ) -> "AISecret": + return cast( + "AISecret", + self.execute( + POST_AI_SECRET, + body=body, + ), + ) + + def put_ai_secret( + self, + *, + body: "CreateAISecret", + ) -> "AISecret": + return cast( + "AISecret", + self.execute( + PUT_AI_SECRET, + body=body, + ), + ) + + def delete_ai_secret( + self, + *, + body: "DeleteAISecret", + ) -> "AISecret": + return cast( + "AISecret", + self.execute( + DELETE_AI_SECRET, + body=body, + ), + ) + + def get_ai_secret( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + ai_secret_name: "AiSecretName | None" = None, + org_name: "OrgName | None" = None, + ai_secret_type: "AISecretType | None" = None, + ) -> "GetAiSecretResponse": + return cast( + "GetAiSecretResponse", + self.execute( + GET_AI_SECRET, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "ai_secret_name": ai_secret_name, + "org_name": org_name, + "ai_secret_type": ai_secret_type, + }, + ), + ) + + def get_ai_secret_id( + self, + ai_secret_id: "AiSecretIdParam", + ) -> "AISecret": + return cast( + "AISecret", + self.execute( + GET_AI_SECRET_ID, + path_parameters={"ai_secret_id": ai_secret_id}, + ), + ) + + def patch_ai_secret_id( + self, + ai_secret_id: "AiSecretIdParam", + *, + body: "PatchAISecret | None" = None, + ) -> "AISecret": + return cast( + "AISecret", + self.execute( + PATCH_AI_SECRET_ID, + path_parameters={"ai_secret_id": ai_secret_id}, + body=body, + ), + ) + + def delete_ai_secret_id( + self, + ai_secret_id: "AiSecretIdParam", + ) -> "AISecret": + return cast( + "AISecret", + self.execute( + DELETE_AI_SECRET_ID, + path_parameters={"ai_secret_id": ai_secret_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/api_keys.py b/py/src/braintrust/api/_generated/api_keys.py new file mode 100644 index 00000000..0eabb1e6 --- /dev/null +++ b/py/src/braintrust/api/_generated/api_keys.py @@ -0,0 +1,163 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: f5a0286093c954f653d2e7b36cdc1a029cfec9605bcbe99d2e1f86dd9f394470 + +"""Generated ApiKeys REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.api_keys import ApiKey, ApiKeyIdParam, ApiKeyName, GetApiKeyResponse +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter + + +GET_API_KEY = Operation( + operation_id="getApiKey", + method="GET", + path="/v1/api_key", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="api_key_name", + name="api_key_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_API_KEY_ID = Operation( + operation_id="getApiKeyId", + method="GET", + path="/v1/api_key/{api_key_id}", + parameters=( + Parameter( + argument_name="api_key_id", + name="api_key_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +DELETE_API_KEY_ID = Operation( + operation_id="deleteApiKeyId", + method="DELETE", + path="/v1/api_key/{api_key_id}", + parameters=( + Parameter( + argument_name="api_key_id", + name="api_key_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "getApiKey": GET_API_KEY, + "getApiKeyId": GET_API_KEY_ID, + "deleteApiKeyId": DELETE_API_KEY_ID, +} + + +class ApiKeysAPI(ResourceAPI): + """Generated ApiKeys REST API.""" + + def get_api_key( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + api_key_name: "ApiKeyName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetApiKeyResponse": + return cast( + "GetApiKeyResponse", + self.execute( + GET_API_KEY, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "api_key_name": api_key_name, + "org_name": org_name, + }, + ), + ) + + def get_api_key_id( + self, + api_key_id: "ApiKeyIdParam", + ) -> "ApiKey": + return cast( + "ApiKey", + self.execute( + GET_API_KEY_ID, + path_parameters={"api_key_id": api_key_id}, + ), + ) + + def delete_api_key_id( + self, + api_key_id: "ApiKeyIdParam", + ) -> "ApiKey": + return cast( + "ApiKey", + self.execute( + DELETE_API_KEY_ID, + path_parameters={"api_key_id": api_key_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/dataset_snapshots.py b/py/src/braintrust/api/_generated/dataset_snapshots.py new file mode 100644 index 00000000..91ce8823 --- /dev/null +++ b/py/src/braintrust/api/_generated/dataset_snapshots.py @@ -0,0 +1,257 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: bd03902630c3edc2d4a6b9416f9827ae3690526fe0fa3c20ee691b76b5bb2a60 + +"""Generated DatasetSnapshots REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.dataset_snapshots import ( + CreateDatasetSnapshot, + DatasetSnapshot, + DatasetSnapshotIdParam, + DatasetSnapshotName, + GetDatasetSnapshotResponse, + PatchDatasetSnapshot, +) + + +POST_DATASET_SNAPSHOT = Operation( + operation_id="postDatasetSnapshot", + method="POST", + path="/v1/dataset_snapshot", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_DATASET_SNAPSHOT = Operation( + operation_id="putDatasetSnapshot", + method="PUT", + path="/v1/dataset_snapshot", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_DATASET_SNAPSHOT = Operation( + operation_id="getDatasetSnapshot", + method="GET", + path="/v1/dataset_snapshot", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="dataset_snapshot_name", + name="dataset_snapshot_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_DATASET_SNAPSHOT_ID = Operation( + operation_id="getDatasetSnapshotId", + method="GET", + path="/v1/dataset_snapshot/{dataset_snapshot_id}", + parameters=( + Parameter( + argument_name="dataset_snapshot_id", + name="dataset_snapshot_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_DATASET_SNAPSHOT_ID = Operation( + operation_id="patchDatasetSnapshotId", + method="PATCH", + path="/v1/dataset_snapshot/{dataset_snapshot_id}", + parameters=( + Parameter( + argument_name="dataset_snapshot_id", + name="dataset_snapshot_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_DATASET_SNAPSHOT_ID = Operation( + operation_id="deleteDatasetSnapshotId", + method="DELETE", + path="/v1/dataset_snapshot/{dataset_snapshot_id}", + parameters=( + Parameter( + argument_name="dataset_snapshot_id", + name="dataset_snapshot_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postDatasetSnapshot": POST_DATASET_SNAPSHOT, + "putDatasetSnapshot": PUT_DATASET_SNAPSHOT, + "getDatasetSnapshot": GET_DATASET_SNAPSHOT, + "getDatasetSnapshotId": GET_DATASET_SNAPSHOT_ID, + "patchDatasetSnapshotId": PATCH_DATASET_SNAPSHOT_ID, + "deleteDatasetSnapshotId": DELETE_DATASET_SNAPSHOT_ID, +} + + +class DatasetSnapshotsAPI(ResourceAPI): + """Generated DatasetSnapshots REST API.""" + + def post_dataset_snapshot( + self, + *, + body: "CreateDatasetSnapshot", + ) -> "DatasetSnapshot": + return cast( + "DatasetSnapshot", + self.execute( + POST_DATASET_SNAPSHOT, + body=body, + ), + ) + + def put_dataset_snapshot( + self, + *, + body: "CreateDatasetSnapshot", + ) -> "DatasetSnapshot": + return cast( + "DatasetSnapshot", + self.execute( + PUT_DATASET_SNAPSHOT, + body=body, + ), + ) + + def get_dataset_snapshot( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + dataset_snapshot_name: "DatasetSnapshotName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetDatasetSnapshotResponse": + return cast( + "GetDatasetSnapshotResponse", + self.execute( + GET_DATASET_SNAPSHOT, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "dataset_snapshot_name": dataset_snapshot_name, + "org_name": org_name, + }, + ), + ) + + def get_dataset_snapshot_id( + self, + dataset_snapshot_id: "DatasetSnapshotIdParam", + ) -> "DatasetSnapshot": + return cast( + "DatasetSnapshot", + self.execute( + GET_DATASET_SNAPSHOT_ID, + path_parameters={"dataset_snapshot_id": dataset_snapshot_id}, + ), + ) + + def patch_dataset_snapshot_id( + self, + dataset_snapshot_id: "DatasetSnapshotIdParam", + *, + body: "PatchDatasetSnapshot | None" = None, + ) -> "DatasetSnapshot": + return cast( + "DatasetSnapshot", + self.execute( + PATCH_DATASET_SNAPSHOT_ID, + path_parameters={"dataset_snapshot_id": dataset_snapshot_id}, + body=body, + ), + ) + + def delete_dataset_snapshot_id( + self, + dataset_snapshot_id: "DatasetSnapshotIdParam", + ) -> "DatasetSnapshot": + return cast( + "DatasetSnapshot", + self.execute( + DELETE_DATASET_SNAPSHOT_ID, + path_parameters={"dataset_snapshot_id": dataset_snapshot_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/env_vars.py b/py/src/braintrust/api/_generated/env_vars.py new file mode 100644 index 00000000..c0b9e4ef --- /dev/null +++ b/py/src/braintrust/api/_generated/env_vars.py @@ -0,0 +1,244 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: e6f206dca4fc4e8716ec42f88ed985b4aeaa3abaf6a161cb7c0753e82582fc8a + +"""Generated EnvVars REST operations and resource.""" + +from collections.abc import Mapping + +from typing import Any, cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, Ids +from .models.env_vars import EnvVar, EnvVarIdParam, EnvVarName, EnvVarObjectId, EnvVarObjectType, GetEnvVarResponse + + +POST_ENV_VAR = Operation( + operation_id="postEnvVar", + method="POST", + path="/v1/env_var", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_ENV_VAR = Operation( + operation_id="putEnvVar", + method="PUT", + path="/v1/env_var", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_ENV_VAR = Operation( + operation_id="getEnvVar", + method="GET", + path="/v1/env_var", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="env_var_name", + name="env_var_name", + location="query", + required=False, + ), + Parameter( + argument_name="object_type", + name="object_type", + location="query", + required=False, + ), + Parameter( + argument_name="object_id", + name="object_id", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_ENV_VAR_ID = Operation( + operation_id="getEnvVarId", + method="GET", + path="/v1/env_var/{env_var_id}", + parameters=( + Parameter( + argument_name="env_var_id", + name="env_var_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_ENV_VAR_ID = Operation( + operation_id="patchEnvVarId", + method="PATCH", + path="/v1/env_var/{env_var_id}", + parameters=( + Parameter( + argument_name="env_var_id", + name="env_var_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_ENV_VAR_ID = Operation( + operation_id="deleteEnvVarId", + method="DELETE", + path="/v1/env_var/{env_var_id}", + parameters=( + Parameter( + argument_name="env_var_id", + name="env_var_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postEnvVar": POST_ENV_VAR, + "putEnvVar": PUT_ENV_VAR, + "getEnvVar": GET_ENV_VAR, + "getEnvVarId": GET_ENV_VAR_ID, + "patchEnvVarId": PATCH_ENV_VAR_ID, + "deleteEnvVarId": DELETE_ENV_VAR_ID, +} + + +class EnvVarsAPI(ResourceAPI): + """Generated EnvVars REST API.""" + + def post_env_var( + self, + *, + body: "Mapping[str, Any]", + ) -> "EnvVar": + return cast( + "EnvVar", + self.execute( + POST_ENV_VAR, + body=body, + ), + ) + + def put_env_var( + self, + *, + body: "Mapping[str, Any]", + ) -> "EnvVar": + return cast( + "EnvVar", + self.execute( + PUT_ENV_VAR, + body=body, + ), + ) + + def get_env_var( + self, + *, + limit: "AppLimitParam | None" = None, + ids: "Ids | None" = None, + env_var_name: "EnvVarName | None" = None, + object_type: "EnvVarObjectType | None" = None, + object_id: "EnvVarObjectId | None" = None, + ) -> "GetEnvVarResponse": + return cast( + "GetEnvVarResponse", + self.execute( + GET_ENV_VAR, + query_parameters={ + "limit": limit, + "ids": ids, + "env_var_name": env_var_name, + "object_type": object_type, + "object_id": object_id, + }, + ), + ) + + def get_env_var_id( + self, + env_var_id: "EnvVarIdParam", + ) -> "EnvVar": + return cast( + "EnvVar", + self.execute( + GET_ENV_VAR_ID, + path_parameters={"env_var_id": env_var_id}, + ), + ) + + def patch_env_var_id( + self, + env_var_id: "EnvVarIdParam", + *, + body: "Mapping[str, Any]", + ) -> "EnvVar": + return cast( + "EnvVar", + self.execute( + PATCH_ENV_VAR_ID, + path_parameters={"env_var_id": env_var_id}, + body=body, + ), + ) + + def delete_env_var_id( + self, + env_var_id: "EnvVarIdParam", + ) -> "EnvVar": + return cast( + "EnvVar", + self.execute( + DELETE_ENV_VAR_ID, + path_parameters={"env_var_id": env_var_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/environments.py b/py/src/braintrust/api/_generated/environments.py new file mode 100644 index 00000000..72625cbc --- /dev/null +++ b/py/src/braintrust/api/_generated/environments.py @@ -0,0 +1,198 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: d73bd03a77ebd1c03ee77908c684087905576b76a1bd1ecf816bbecf83386272 + +"""Generated Environments REST operations and resource.""" + +from collections.abc import Sequence + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import OrgName +from .models.environments import CreateEnvironment, Environment, ListEnvironmentsResponse, PatchEnvironment + + +LIST_ENVIRONMENTS = Operation( + operation_id="listEnvironments", + method="GET", + path="/environment", + parameters=( + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="name", + name="name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +CREATE_ENVIRONMENT = Operation( + operation_id="createEnvironment", + method="POST", + path="/environment", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_ENVIRONMENT = Operation( + operation_id="getEnvironment", + method="GET", + path="/environment/{environment_id}", + parameters=( + Parameter( + argument_name="environment_id", + name="environment_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +UPDATE_ENVIRONMENT = Operation( + operation_id="updateEnvironment", + method="PATCH", + path="/environment/{environment_id}", + parameters=( + Parameter( + argument_name="environment_id", + name="environment_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_ENVIRONMENT = Operation( + operation_id="deleteEnvironment", + method="DELETE", + path="/environment/{environment_id}", + parameters=( + Parameter( + argument_name="environment_id", + name="environment_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "listEnvironments": LIST_ENVIRONMENTS, + "createEnvironment": CREATE_ENVIRONMENT, + "getEnvironment": GET_ENVIRONMENT, + "updateEnvironment": UPDATE_ENVIRONMENT, + "deleteEnvironment": DELETE_ENVIRONMENT, +} + + +class EnvironmentsAPI(ResourceAPI): + """Generated Environments REST API.""" + + def list_environments( + self, + *, + ids: "str | Sequence[str] | None" = None, + name: "str | None" = None, + org_name: "OrgName | None" = None, + ) -> "ListEnvironmentsResponse": + return cast( + "ListEnvironmentsResponse", + self.execute( + LIST_ENVIRONMENTS, + query_parameters={"ids": ids, "name": name, "org_name": org_name}, + ), + ) + + def create_environment( + self, + *, + body: "CreateEnvironment | None" = None, + ) -> "Environment": + return cast( + "Environment", + self.execute( + CREATE_ENVIRONMENT, + body=body, + ), + ) + + def get_environment( + self, + environment_id: "str", + ) -> "Environment": + return cast( + "Environment", + self.execute( + GET_ENVIRONMENT, + path_parameters={"environment_id": environment_id}, + ), + ) + + def update_environment( + self, + environment_id: "str", + *, + body: "PatchEnvironment | None" = None, + ) -> "Environment": + return cast( + "Environment", + self.execute( + UPDATE_ENVIRONMENT, + path_parameters={"environment_id": environment_id}, + body=body, + ), + ) + + def delete_environment( + self, + environment_id: "str", + ) -> "Environment": + return cast( + "Environment", + self.execute( + DELETE_ENVIRONMENT, + path_parameters={"environment_id": environment_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/groups.py b/py/src/braintrust/api/_generated/groups.py new file mode 100644 index 00000000..34275b5e --- /dev/null +++ b/py/src/braintrust/api/_generated/groups.py @@ -0,0 +1,250 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: cb45f2d3342d4d19d7be553dfd1f5983b4ac76da3967fb3fb361d65e2cfec373 + +"""Generated Groups REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.groups import CreateGroup, GetGroupResponse, Group, GroupIdParam, GroupName, PatchGroup + + +POST_GROUP = Operation( + operation_id="postGroup", + method="POST", + path="/v1/group", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_GROUP = Operation( + operation_id="putGroup", + method="PUT", + path="/v1/group", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_GROUP = Operation( + operation_id="getGroup", + method="GET", + path="/v1/group", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="group_name", + name="group_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_GROUP_ID = Operation( + operation_id="getGroupId", + method="GET", + path="/v1/group/{group_id}", + parameters=( + Parameter( + argument_name="group_id", + name="group_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_GROUP_ID = Operation( + operation_id="patchGroupId", + method="PATCH", + path="/v1/group/{group_id}", + parameters=( + Parameter( + argument_name="group_id", + name="group_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_GROUP_ID = Operation( + operation_id="deleteGroupId", + method="DELETE", + path="/v1/group/{group_id}", + parameters=( + Parameter( + argument_name="group_id", + name="group_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postGroup": POST_GROUP, + "putGroup": PUT_GROUP, + "getGroup": GET_GROUP, + "getGroupId": GET_GROUP_ID, + "patchGroupId": PATCH_GROUP_ID, + "deleteGroupId": DELETE_GROUP_ID, +} + + +class GroupsAPI(ResourceAPI): + """Generated Groups REST API.""" + + def post_group( + self, + *, + body: "CreateGroup", + ) -> "Group": + return cast( + "Group", + self.execute( + POST_GROUP, + body=body, + ), + ) + + def put_group( + self, + *, + body: "CreateGroup", + ) -> "Group": + return cast( + "Group", + self.execute( + PUT_GROUP, + body=body, + ), + ) + + def get_group( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + group_name: "GroupName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetGroupResponse": + return cast( + "GetGroupResponse", + self.execute( + GET_GROUP, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "group_name": group_name, + "org_name": org_name, + }, + ), + ) + + def get_group_id( + self, + group_id: "GroupIdParam", + ) -> "Group": + return cast( + "Group", + self.execute( + GET_GROUP_ID, + path_parameters={"group_id": group_id}, + ), + ) + + def patch_group_id( + self, + group_id: "GroupIdParam", + *, + body: "PatchGroup | None" = None, + ) -> "Group": + return cast( + "Group", + self.execute( + PATCH_GROUP_ID, + path_parameters={"group_id": group_id}, + body=body, + ), + ) + + def delete_group_id( + self, + group_id: "GroupIdParam", + ) -> "Group": + return cast( + "Group", + self.execute( + DELETE_GROUP_ID, + path_parameters={"group_id": group_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/mcp_servers.py b/py/src/braintrust/api/_generated/mcp_servers.py new file mode 100644 index 00000000..adcdbdce --- /dev/null +++ b/py/src/braintrust/api/_generated/mcp_servers.py @@ -0,0 +1,257 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 0afa41da9e0b2ef95b538eb2812d9ae48d805484820b215f7241c4ac4ef33ea7 + +"""Generated McpServers REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.mcp_servers import ( + CreateMCPServer, + GetMcpServerResponse, + MCPServer, + McpServerIdParam, + McpServerName, + PatchMCPServer, +) + + +POST_MCP_SERVER = Operation( + operation_id="postMcpServer", + method="POST", + path="/v1/mcp_server", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_MCP_SERVER = Operation( + operation_id="putMcpServer", + method="PUT", + path="/v1/mcp_server", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_MCP_SERVER = Operation( + operation_id="getMcpServer", + method="GET", + path="/v1/mcp_server", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="mcp_server_name", + name="mcp_server_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_MCP_SERVER_ID = Operation( + operation_id="getMcpServerId", + method="GET", + path="/v1/mcp_server/{mcp_server_id}", + parameters=( + Parameter( + argument_name="mcp_server_id", + name="mcp_server_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_MCP_SERVER_ID = Operation( + operation_id="patchMcpServerId", + method="PATCH", + path="/v1/mcp_server/{mcp_server_id}", + parameters=( + Parameter( + argument_name="mcp_server_id", + name="mcp_server_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_MCP_SERVER_ID = Operation( + operation_id="deleteMcpServerId", + method="DELETE", + path="/v1/mcp_server/{mcp_server_id}", + parameters=( + Parameter( + argument_name="mcp_server_id", + name="mcp_server_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postMcpServer": POST_MCP_SERVER, + "putMcpServer": PUT_MCP_SERVER, + "getMcpServer": GET_MCP_SERVER, + "getMcpServerId": GET_MCP_SERVER_ID, + "patchMcpServerId": PATCH_MCP_SERVER_ID, + "deleteMcpServerId": DELETE_MCP_SERVER_ID, +} + + +class McpServersAPI(ResourceAPI): + """Generated McpServers REST API.""" + + def post_mcp_server( + self, + *, + body: "CreateMCPServer", + ) -> "MCPServer": + return cast( + "MCPServer", + self.execute( + POST_MCP_SERVER, + body=body, + ), + ) + + def put_mcp_server( + self, + *, + body: "CreateMCPServer", + ) -> "MCPServer": + return cast( + "MCPServer", + self.execute( + PUT_MCP_SERVER, + body=body, + ), + ) + + def get_mcp_server( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + mcp_server_name: "McpServerName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetMcpServerResponse": + return cast( + "GetMcpServerResponse", + self.execute( + GET_MCP_SERVER, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "mcp_server_name": mcp_server_name, + "org_name": org_name, + }, + ), + ) + + def get_mcp_server_id( + self, + mcp_server_id: "McpServerIdParam", + ) -> "MCPServer": + return cast( + "MCPServer", + self.execute( + GET_MCP_SERVER_ID, + path_parameters={"mcp_server_id": mcp_server_id}, + ), + ) + + def patch_mcp_server_id( + self, + mcp_server_id: "McpServerIdParam", + *, + body: "PatchMCPServer | None" = None, + ) -> "MCPServer": + return cast( + "MCPServer", + self.execute( + PATCH_MCP_SERVER_ID, + path_parameters={"mcp_server_id": mcp_server_id}, + body=body, + ), + ) + + def delete_mcp_server_id( + self, + mcp_server_id: "McpServerIdParam", + ) -> "MCPServer": + return cast( + "MCPServer", + self.execute( + DELETE_MCP_SERVER_ID, + path_parameters={"mcp_server_id": mcp_server_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/models/__init__.py b/py/src/braintrust/api/_generated/models/__init__.py index 9fdb1765..fabe1e15 100644 --- a/py/src/braintrust/api/_generated/models/__init__.py +++ b/py/src/braintrust/api/_generated/models/__init__.py @@ -4,12 +4,43 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: e704e1b6be9125a28feae60cfc2cd941250bdab6c9f3bf7f08246770d4b6bfa1 +# Content SHA-256: c1dd7cb41842e16a5d40bf416da033db86114bb941267df342fe065e13872c83 """Generated private model types with stable package-level imports.""" +from .acls import ( + Acl, + AclBatchUpdateRequest, + AclBatchUpdateResponse, + AclIdParam, + AclItem, + AclListGroupId, + AclListOrgObjectId, + AclListOrgObjectType, + AclListOrgResponse, + AclListPermission, + AclListRestrictObjectType, + AclListRoleId, + AclListUserId, + GetAclResponse, +) +from .agents import Agent, AgentIdParam, AgentName, CreateAgent, GetAgentResponse, PatchAgent +from .ai_secrets import ( + AISecret, + AISecretType, + AiSecretIdParam, + AiSecretName, + CreateAISecret, + DeleteAISecret, + GetAiSecretResponse, + PatchAISecret, +) +from .api_keys import ApiKey, ApiKeyIdParam, ApiKeyName, GetApiKeyResponse from .common import ( + AclObjectId, + AclObjectType, AppLimitParam, + AutomationStatus, CacheControl, ChatCompletionContentPart, ChatCompletionContentPartFileFile, @@ -29,6 +60,7 @@ ChatCompletionMessageToolCall, ChatCompletionMessageToolCallFunction, Classification, + Config4, EndingBefore, FeedbackResponseSchema, FetchEventsRequest, @@ -40,6 +72,7 @@ FunctionCall1, FunctionTypeEnum, FunctionTypeEnumNullish, + GroupScope, Ids, ImageUrl, InsertEventsResponse, @@ -58,6 +91,7 @@ ObjectReferenceNullish, OrgName, Origin2, + Permission, PreprocessorId, PreprocessorId1, PreprocessorId2, @@ -77,10 +111,12 @@ ResponseFormatNullish1, ResponseFormatNullish2, ResponseFormatNullish3, + RetentionObjectType, SavedFunctionId, SavedFunctionId1, SavedFunctionId2, Slug, + SpanScope, StartingAfter, ToolChoice, ToolFunction, @@ -91,8 +127,17 @@ ToolFunction5, ToolFunction6, ToolFunction7, + TraceScope, Version, ) +from .dataset_snapshots import ( + CreateDatasetSnapshot, + DatasetSnapshot, + DatasetSnapshotIdParam, + DatasetSnapshotName, + GetDatasetSnapshotResponse, + PatchDatasetSnapshot, +) from .datasets import ( CreateDataset, DataSummary, @@ -110,6 +155,8 @@ SummarizeData, SummarizeDatasetResponse, ) +from .env_vars import EnvVar, EnvVarIdParam, EnvVarName, EnvVarObjectId, EnvVarObjectType, GetEnvVarResponse +from .environments import CreateEnvironment, Environment, ListEnvironmentsResponse, PatchEnvironment from .experiments import ( AppLimitWithDefaultParam, ComparisonExperimentId, @@ -137,7 +184,6 @@ SummarizeScores, ) from .functions import ( - AclObjectType, BatchedFacetData, CodeBundle, CreateFunction, @@ -210,6 +256,163 @@ TopicMapData, TopicMapGenerationSettings, ) +from .groups import CreateGroup, GetGroupResponse, Group, GroupIdParam, GroupName, PatchGroup +from .mcp_servers import ( + CreateMCPServer, + GetMcpServerResponse, + MCPServer, + McpServerIdParam, + McpServerName, + PatchMCPServer, +) +from .org_automations import ( + Config, + CreateOrgAutomation, + GetOrgAutomationResponse, + OrgAutomation, + OrgAutomationIdParam, + OrgAutomationName, + PatchOrgAutomation, +) +from .organizations import ( + AddedUser, + GetOrganizationResponse, + ImageRenderingMode, + InviteUsers, + Organization, + OrganizationIdParam, + PatchOrganization, + PatchOrganizationMembers, + PatchOrganizationMembersOutput, + RemoveUsers, + ServiceAccount, +) +from .project_automations import ( + Action, + Action1, + Action10, + Action11, + Action2, + Action3, + Action4, + Action5, + Action6, + Action7, + Action8, + Action9, + Actions, + Actions1, + BackfillTimeRange, + Calculation, + Condition, + Config1, + Config10, + Config11, + Config12, + Config13, + Config14, + Config15, + Config16, + Config17, + Config2, + Config3, + Config5, + Config8, + Config9, + CreateProjectAutomation, + Credentials, + Credentials1, + Credentials2, + Credentials3, + Credentials4, + Credentials5, + ExportDefinition, + ExportDefinition1, + ExportDefinition2, + ExportDefinition3, + ExportDefinition4, + ExportDefinition5, + ExportDefinition6, + ExportDefinition7, + ExportDefinition8, + FacetFunction, + FacetFunction1, + FacetFunction2, + FacetFunction3, + FacetFunction4, + FacetFunction5, + FacetFunction6, + FacetFunction7, + Function1, + Function11, + Function12, + Function13, + Function14, + Function15, + Function16, + Function17, + GetProjectAutomationResponse, + Loop, + Output, + PatchProjectAutomation, + Policy, + ProjectAutomation, + ProjectAutomationIdParam, + ProjectAutomationName, + Schedule, + Schedule1, + Threshold, + TopicAutomationConfig, + TopicAutomationDataScope, + TopicAutomationDataScope1, + TopicAutomationDataScope2, + TopicAutomationDataScope3, + TopicAutomationFacetModel, + TopicDigestAutomationConfig, + TopicMapFunctionAutomation, + Window, + WindowedAutomationConfig, +) +from .project_groups import ( + CreateProjectGroup, + GetProjectGroupResponse, + PatchProjectGroup, + ProjectGroup, + ProjectGroupIdParam, + ProjectGroupName, +) +from .project_scores import ( + CreateProjectScore, + GetProjectScoreResponse, + OnlineScoreConfig, + PatchProjectScore, + ProjectScore, + ProjectScoreCategories, + ProjectScoreCategory, + ProjectScoreCondition, + ProjectScoreConfig, + ProjectScoreIdParam, + ProjectScoreName, + ProjectScoreType, + Scorer, + Scorer1, + Scorer2, + Scorer3, + Scorer4, + Scorer5, + Scorer6, + Scorer7, + Visibility, + When, +) +from .project_tags import ( + CreateProjectTag, + GetProjectTagResponse, + PatchProjectTag, + ProjectTag, + ProjectTagIdParam, + ProjectTagName, +) from .projects import ( CreateProject, GetProjectResponse, @@ -224,14 +427,109 @@ SpanFieldOrderItem, ) from .prompts import CreatePrompt, GetPromptResponse, PatchPrompt, Prompt, PromptIdParam, PromptName +from .roles import ( + AddMemberPermission, + CreateRole, + GetRoleResponse, + MemberPermission, + PatchRole, + RemoveMemberPermission, + Role, + RoleIdParam, + RoleName, +) +from .service_tokens import ( + CreateServiceTokenOutput, + DeleteServiceToken, + GetServiceTokenResponse, + ServiceToken, + ServiceTokenIdParam, + ServiceTokenName, +) +from .span_iframes import ( + CreateSpanIFrame, + GetSpanIframeResponse, + PatchSpanIFrame, + SpanIFrame, + SpanIframeIdParam, + SpanIframeName, +) +from .users import GetUserResponse, User, UserEmail, UserFamilyName, UserGivenName, UserIdParam +from .views import ( + ChartAnnotation, + CreateView, + DeleteView, + ExcludedMeasure, + GetViewResponse, + Options, + PatchView, + PointSizeMetric, + SymbolGrouping, + TimeRangeFilter, + View, + ViewData, + ViewDataSearch, + ViewIdParam, + ViewName, + ViewOptions, + ViewOptions1, + ViewOptions2, + ViewType, + XAxis, + YMetric, +) __all__ = [ + "AISecret", + "AISecretType", + "Acl", + "AclBatchUpdateRequest", + "AclBatchUpdateResponse", + "AclIdParam", + "AclItem", + "AclListGroupId", + "AclListOrgObjectId", + "AclListOrgObjectType", + "AclListOrgResponse", + "AclListPermission", + "AclListRestrictObjectType", + "AclListRoleId", + "AclListUserId", + "AclObjectId", "AclObjectType", + "Action", + "Action1", + "Action10", + "Action11", + "Action2", + "Action3", + "Action4", + "Action5", + "Action6", + "Action7", + "Action8", + "Action9", + "Actions", + "Actions1", + "AddMemberPermission", + "AddedUser", + "Agent", + "AgentIdParam", + "AgentName", + "AiSecretIdParam", + "AiSecretName", + "ApiKey", + "ApiKeyIdParam", + "ApiKeyName", "AppLimitParam", "AppLimitWithDefaultParam", + "AutomationStatus", + "BackfillTimeRange", "BatchedFacetData", "CacheControl", + "Calculation", + "ChartAnnotation", "ChatCompletionContentPart", "ChatCompletionContentPartFileFile", "ChatCompletionContentPartFileWithTitle", @@ -252,12 +550,50 @@ "Classification", "CodeBundle", "ComparisonExperimentId", + "Condition", + "Config", + "Config1", + "Config10", + "Config11", + "Config12", + "Config13", + "Config14", + "Config15", + "Config16", + "Config17", + "Config2", + "Config3", + "Config4", + "Config5", + "Config8", + "Config9", "Context", + "CreateAISecret", + "CreateAgent", "CreateDataset", + "CreateDatasetSnapshot", + "CreateEnvironment", "CreateExperiment", "CreateFunction", + "CreateGroup", + "CreateMCPServer", + "CreateOrgAutomation", "CreateProject", + "CreateProjectAutomation", + "CreateProjectGroup", + "CreateProjectScore", + "CreateProjectTag", "CreatePrompt", + "CreateRole", + "CreateServiceTokenOutput", + "CreateSpanIFrame", + "CreateView", + "Credentials", + "Credentials1", + "Credentials2", + "Credentials3", + "Credentials4", + "Credentials5", "Data", "Data1", "Data2", @@ -267,13 +603,43 @@ "DatasetEvent", "DatasetIdParam", "DatasetName", + "DatasetSnapshot", + "DatasetSnapshotIdParam", + "DatasetSnapshotName", + "DeleteAISecret", + "DeleteServiceToken", + "DeleteView", "EndingBefore", + "EnvVar", + "EnvVarIdParam", + "EnvVarName", + "EnvVarObjectId", + "EnvVarObjectType", + "Environment", + "ExcludedMeasure", "Experiment", "ExperimentEvent", "ExperimentIdParam", "ExperimentName", + "ExportDefinition", + "ExportDefinition1", + "ExportDefinition2", + "ExportDefinition3", + "ExportDefinition4", + "ExportDefinition5", + "ExportDefinition6", + "ExportDefinition7", + "ExportDefinition8", "Facet", "FacetData", + "FacetFunction", + "FacetFunction1", + "FacetFunction2", + "FacetFunction3", + "FacetFunction4", + "FacetFunction5", + "FacetFunction6", + "FacetFunction7", "FacetPreprocessorId", "FacetPreprocessorId1", "FacetPreprocessorId2", @@ -292,6 +658,14 @@ "FieldArrayDeleteItem", "FieldSchema", "Function", + "Function1", + "Function11", + "Function12", + "Function13", + "Function14", + "Function15", + "Function16", + "Function17", "FunctionCall", "FunctionCall1", "FunctionData", @@ -312,11 +686,30 @@ "FunctionSchema", "FunctionTypeEnum", "FunctionTypeEnumNullish", + "GetAclResponse", + "GetAgentResponse", + "GetAiSecretResponse", + "GetApiKeyResponse", "GetDatasetResponse", + "GetDatasetSnapshotResponse", + "GetEnvVarResponse", "GetExperimentResponse", "GetFunctionResponse", + "GetGroupResponse", + "GetMcpServerResponse", + "GetOrgAutomationResponse", + "GetOrganizationResponse", + "GetProjectAutomationResponse", + "GetProjectGroupResponse", "GetProjectResponse", + "GetProjectScoreResponse", + "GetProjectTagResponse", "GetPromptResponse", + "GetRoleResponse", + "GetServiceTokenResponse", + "GetSpanIframeResponse", + "GetUserResponse", + "GetViewResponse", "GraphData", "GraphEdge", "GraphNode", @@ -328,7 +721,12 @@ "GraphNode6", "GraphNode7", "GraphNode8", + "Group", + "GroupIdParam", + "GroupName", + "GroupScope", "Ids", + "ImageRenderingMode", "ImageUrl", "InsertDatasetEvent", "InsertDatasetEventRequest", @@ -336,13 +734,20 @@ "InsertExperimentEvent", "InsertExperimentEventRequest", "InternalMetadata", + "InviteUsers", + "ListEnvironmentsResponse", "Location", "Location1", "Location2", + "Loop", + "MCPServer", "MaxRootSpanId", "MaxXactId", "Mcp", "Mcp1", + "McpServerIdParam", + "McpServerName", + "MemberPermission", "Metadata", "MetricSummary", "Metrics", @@ -357,14 +762,42 @@ "NullableSavedFunctionId1", "NullableSavedFunctionId2", "ObjectReferenceNullish", + "OnlineScoreConfig", + "Options", + "OrgAutomation", + "OrgAutomationIdParam", + "OrgAutomationName", "OrgName", + "Organization", + "OrganizationIdParam", "Origin", "Origin2", + "Output", + "PatchAISecret", + "PatchAgent", "PatchDataset", + "PatchDatasetSnapshot", + "PatchEnvironment", "PatchExperiment", "PatchFunction", + "PatchGroup", + "PatchMCPServer", + "PatchOrgAutomation", + "PatchOrganization", + "PatchOrganizationMembers", + "PatchOrganizationMembersOutput", "PatchProject", + "PatchProjectAutomation", + "PatchProjectGroup", + "PatchProjectScore", + "PatchProjectTag", "PatchPrompt", + "PatchRole", + "PatchSpanIFrame", + "PatchView", + "Permission", + "PointSizeMetric", + "Policy", "Position", "Position1", "Position2", @@ -374,10 +807,27 @@ "PreprocessorId2", "PreprocessorId3", "Project", + "ProjectAutomation", + "ProjectAutomationIdParam", + "ProjectAutomationName", + "ProjectGroup", + "ProjectGroupIdParam", + "ProjectGroupName", "ProjectIdParam", "ProjectIdQuery", "ProjectName", + "ProjectScore", + "ProjectScoreCategories", + "ProjectScoreCategory", + "ProjectScoreCondition", + "ProjectScoreConfig", + "ProjectScoreIdParam", + "ProjectScoreName", + "ProjectScoreType", "ProjectSettings", + "ProjectTag", + "ProjectTagIdParam", + "ProjectTagName", "Prompt", "PromptBlockData", "PromptBlockData1", @@ -393,19 +843,39 @@ "PromptParserNullish", "PromptVersion", "RemoteEvalSource", + "RemoveMemberPermission", + "RemoveUsers", "RepoInfo", "ResponseFormatJsonSchema", "ResponseFormatNullish", "ResponseFormatNullish1", "ResponseFormatNullish2", "ResponseFormatNullish3", + "RetentionObjectType", + "Role", + "RoleIdParam", + "RoleName", "RuntimeContext", "SandboxSpec", "SandboxSpec1", "SavedFunctionId", "SavedFunctionId1", "SavedFunctionId2", + "Schedule", + "Schedule1", "ScoreSummary", + "Scorer", + "Scorer1", + "Scorer2", + "Scorer3", + "Scorer4", + "Scorer5", + "Scorer6", + "Scorer7", + "ServiceAccount", + "ServiceToken", + "ServiceTokenIdParam", + "ServiceTokenName", "Slug", "Source", "SourceFacetFunction", @@ -418,13 +888,20 @@ "SourceFacetFunction7", "SpanAttributes", "SpanFieldOrderItem", + "SpanIFrame", + "SpanIframeIdParam", + "SpanIframeName", + "SpanScope", "SpanType", "StartingAfter", "SummarizeData", "SummarizeDatasetResponse", "SummarizeExperimentResponse", "SummarizeScores", + "SymbolGrouping", "Target", + "Threshold", + "TimeRangeFilter", "ToolChoice", "ToolFunction", "ToolFunction1", @@ -434,8 +911,37 @@ "ToolFunction5", "ToolFunction6", "ToolFunction7", + "TopicAutomationConfig", + "TopicAutomationDataScope", + "TopicAutomationDataScope1", + "TopicAutomationDataScope2", + "TopicAutomationDataScope3", + "TopicAutomationFacetModel", + "TopicDigestAutomationConfig", "TopicMap", "TopicMapData", + "TopicMapFunctionAutomation", "TopicMapGenerationSettings", + "TraceScope", + "User", + "UserEmail", + "UserFamilyName", + "UserGivenName", + "UserIdParam", "Version", + "View", + "ViewData", + "ViewDataSearch", + "ViewIdParam", + "ViewName", + "ViewOptions", + "ViewOptions1", + "ViewOptions2", + "ViewType", + "Visibility", + "When", + "Window", + "WindowedAutomationConfig", + "XAxis", + "YMetric", ] diff --git a/py/src/braintrust/api/_generated/models/acls.py b/py/src/braintrust/api/_generated/models/acls.py new file mode 100644 index 00000000..8108b450 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/acls.py @@ -0,0 +1,184 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: d52050e09e8da2a95284c40fcad27478a4585f3124f824d0fea3b1cc10dd105c + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +from .common import AclObjectType, Permission + +AclIdParam: TypeAlias = str +""" +Acl id +""" + +AclListGroupId: TypeAlias = str +""" +Id of the group the ACL applies to. Exactly one of `user_id` and `group_id` will be provided +""" + +AclListOrgObjectId: TypeAlias = str +""" +The id of the object the ACL applies to +""" + +AclListOrgObjectType: TypeAlias = Literal[ + "organization", + "project", + "experiment", + "dataset", + "prompt", + "prompt_session", + "group", + "role", + "org_member", + "project_log", + "org_project", + "org_audit_logs", + "project_group", + "ai_secret", + "org_ai_secret", +] +""" +The object type that the ACL applies to +""" + +AclListPermission: TypeAlias = Literal[ + "create", + "read", + "update", + "delete", + "create_acls", + "read_acls", + "update_acls", + "delete_acls", +] +""" +Each permission permits a certain type of operation on an object in the system + +Permissions can be assigned to to objects on an individual basis, or grouped into roles +""" + +AclListRestrictObjectType: TypeAlias = Literal[ + "organization", + "project", + "experiment", + "dataset", + "prompt", + "prompt_session", + "group", + "role", + "org_member", + "project_log", + "org_project", + "org_audit_logs", + "project_group", + "ai_secret", + "org_ai_secret", +] +""" +The object type that the ACL applies to +""" + +AclListRoleId: TypeAlias = str +""" +Id of the role the ACL grants. Exactly one of `permission` and `role_id` will be provided +""" + +AclListUserId: TypeAlias = str +""" +Id of the user the ACL applies to. Exactly one of `user_id` and `group_id` will be provided +""" + + +class Acl(TypedDict): + _object_org_id: str + """ + The organization the ACL's referred object belongs to + """ + created: NotRequired[str | None] + """ + Date of acl creation + """ + group_id: NotRequired[str | None] + """ + Id of the group the ACL applies to. Exactly one of `user_id` and `group_id` will be provided + """ + id: str + """ + Unique identifier for the acl + """ + object_id: str + """ + The id of the object the ACL applies to + """ + object_type: AclObjectType + permission: NotRequired[Permission | None] + """ + Permission the ACL grants. Exactly one of `permission` and `role_id` will be provided + """ + restrict_object_type: NotRequired[AclObjectType | None] + """ + When setting a permission directly, optionally restricts the permission grant to just the specified object type. Cannot be set alongside a `role_id`. + """ + role_id: NotRequired[str | None] + """ + Id of the role the ACL grants. Exactly one of `permission` and `role_id` will be provided + """ + user_id: NotRequired[str | None] + """ + Id of the user the ACL applies to. Exactly one of `user_id` and `group_id` will be provided + """ + + +class AclBatchUpdateResponse(TypedDict): + added_acls: Sequence[Acl] + removed_acls: Sequence[Acl] + + +class AclItem(TypedDict): + group_id: NotRequired[str | None] + """ + Id of the group the ACL applies to. Exactly one of `user_id` and `group_id` will be provided + """ + object_id: str + """ + The id of the object the ACL applies to + """ + object_type: AclObjectType + permission: NotRequired[Permission | None] + """ + Permission the ACL grants. Exactly one of `permission` and `role_id` will be provided + """ + restrict_object_type: NotRequired[AclObjectType | None] + """ + When setting a permission directly, optionally restricts the permission grant to just the specified object type. Cannot be set alongside a `role_id`. + """ + role_id: NotRequired[str | None] + """ + Id of the role the ACL grants. Exactly one of `permission` and `role_id` will be provided + """ + user_id: NotRequired[str | None] + """ + Id of the user the ACL applies to. Exactly one of `user_id` and `group_id` will be provided + """ + + +AclListOrgResponse: TypeAlias = Sequence[Acl] + + +class GetAclResponse(TypedDict): + objects: Sequence[Acl] + """ + A list of acl objects + """ + + +class AclBatchUpdateRequest(TypedDict): + add_acls: NotRequired[Sequence[AclItem] | None] + remove_acls: NotRequired[Sequence[AclItem] | None] diff --git a/py/src/braintrust/api/_generated/models/agents.py b/py/src/braintrust/api/_generated/models/agents.py new file mode 100644 index 00000000..7d8706c6 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/agents.py @@ -0,0 +1,99 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 768a7ba86547c3d62db1818f63c9cccfeb77584e11d105f3f8ed014603fbb6db + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class Agent(TypedDict): + created: NotRequired[str | None] + """ + Date of agent creation + """ + description: NotRequired[str | None] + """ + Textual description of the agent + """ + id: str + """ + Unique identifier for the agent + """ + kind: str + """ + Agent classification: 'custom' for customer-defined agents, 'loop' for built-in Loop agents. + """ + metadata: NotRequired[Mapping[str, Any] | None] + """ + User-controlled metadata about the agent + """ + name: str + """ + Name of the agent. Within a project, agent names are unique + """ + project_id: str + """ + Unique identifier for the project that the agent belongs under + """ + slug: str + """ + Stable, URL-safe identifier for the agent, unique within its project. + """ + user_id: str + + +AgentIdParam: TypeAlias = str +""" +Agent id +""" + +AgentName: TypeAlias = str +""" +Name of the agent to search for +""" + + +class CreateAgent(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the agent + """ + metadata: NotRequired[Mapping[str, Any] | None] + """ + User-controlled metadata about the agent + """ + name: str + """ + Name of the agent. Within a project, agent names are unique + """ + project_id: str + """ + Unique identifier for the project that the agent belongs under + """ + + +class GetAgentResponse(TypedDict): + objects: Sequence[Agent] + """ + A list of agent objects + """ + + +class PatchAgent(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the agent + """ + metadata: NotRequired[Mapping[str, Any] | None] + """ + User-controlled metadata about the agent + """ + name: NotRequired[str | None] + """ + Name of the agent. Within a project, agent names are unique + """ diff --git a/py/src/braintrust/api/_generated/models/ai_secrets.py b/py/src/braintrust/api/_generated/models/ai_secrets.py new file mode 100644 index 00000000..c4d1b755 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/ai_secrets.py @@ -0,0 +1,103 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: eca638da8cc3bcb1caddd3e11ecd7ae52a79dc78dffabdb0d376ad77c0bc382d + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class AISecret(TypedDict): + created: NotRequired[str | None] + """ + Date of AI secret creation + """ + id: str + """ + Unique identifier for the AI secret + """ + metadata: NotRequired[Mapping[str, Any] | None] + name: str + """ + Name of the AI secret + """ + org_id: str + """ + Unique identifier for the organization + """ + preview_secret: NotRequired[str | None] + secret_updated_at: NotRequired[str | None] + """ + Date of last update to the encrypted secret value itself + """ + secret_updated_by_user_id: NotRequired[str | None] + """ + User id of the last update to the encrypted secret value + """ + type: NotRequired[str | None] + updated_at: NotRequired[str | None] + """ + Date of last AI secret update + """ + + +AISecretType: TypeAlias = str | Sequence[str] + +AiSecretIdParam: TypeAlias = str +""" +AiSecret id +""" + +AiSecretName: TypeAlias = str +""" +Name of the ai_secret to search for +""" + + +class CreateAISecret(TypedDict): + metadata: NotRequired[Mapping[str, Any] | None] + name: str + """ + Name of the AI secret + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, you may specify the name of the organization the AI Secret belongs in. + """ + secret: NotRequired[str | None] + """ + Secret value. If omitted in a PUT request, the existing secret value will be left intact, not replaced with null. + """ + type: NotRequired[str | None] + + +class DeleteAISecret(TypedDict): + name: str + """ + Name of the AI secret + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, you may specify the name of the organization the AI Secret belongs in. + """ + + +class GetAiSecretResponse(TypedDict): + objects: Sequence[AISecret] + """ + A list of ai_secret objects + """ + + +class PatchAISecret(TypedDict): + metadata: NotRequired[Mapping[str, Any] | None] + name: NotRequired[str | None] + """ + Name of the AI secret + """ + secret: NotRequired[str | None] + type: NotRequired[str | None] diff --git a/py/src/braintrust/api/_generated/models/api_keys.py b/py/src/braintrust/api/_generated/models/api_keys.py new file mode 100644 index 00000000..5eac1943 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/api_keys.py @@ -0,0 +1,69 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 50b04c997c9d1bc8a29b0f88d5448dd3d6009b77f60107234e16850d2613df9c + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class ApiKey(TypedDict): + created: NotRequired[str | None] + """ + Date of api key creation + """ + expires_at: NotRequired[str | None] + """ + Date at which the API key expires. If null, the key never expires. + """ + id: str + """ + Unique identifier for the api key + """ + name: str + """ + Name of the api key + """ + org_id: NotRequired[str | None] + """ + Unique identifier for the organization + """ + preview_name: str + user_email: NotRequired[str | None] + """ + The user's email + """ + user_family_name: NotRequired[str | None] + """ + Family name of the user + """ + user_given_name: NotRequired[str | None] + """ + Given name of the user + """ + user_id: NotRequired[str | None] + """ + Unique identifier for the user + """ + + +ApiKeyIdParam: TypeAlias = str +""" +ApiKey id +""" + +ApiKeyName: TypeAlias = str +""" +Name of the api_key to search for +""" + + +class GetApiKeyResponse(TypedDict): + objects: Sequence[ApiKey] + """ + A list of api_key objects + """ diff --git a/py/src/braintrust/api/_generated/models/common.py b/py/src/braintrust/api/_generated/models/common.py index 29253385..e1693d5f 100644 --- a/py/src/braintrust/api/_generated/models/common.py +++ b/py/src/braintrust/api/_generated/models/common.py @@ -4,17 +4,48 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: 7296a2e10cecbbc7116e3a28f496a1170697047817c46f0528b3586c02da920c +# Content SHA-256: 530642e00dbd8338380c8a8417e4227cd7a9ebf6e4f719e9de88dc43c17fe38e -from typing import Any, Literal, TypeAlias, TypedDict from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict from collections.abc import Mapping, Sequence +AclObjectId: TypeAlias = str +""" +The id of the object the ACL applies to +""" + +AclObjectType: TypeAlias = Literal[ + "organization", + "project", + "experiment", + "dataset", + "prompt", + "prompt_session", + "group", + "role", + "org_member", + "project_log", + "org_project", + "org_audit_logs", + "project_group", + "ai_secret", + "org_ai_secret", +] +""" +The object type that the ACL applies to +""" + AppLimitParam: TypeAlias = int | None """ Limit the number of objects to return """ +AutomationStatus: TypeAlias = Literal["active", "paused"] +""" +Whether the automation is active or paused. +""" + class ChatCompletionContentPartFileFile(TypedDict): file_data: NotRequired[str] @@ -181,6 +212,31 @@ class FeedbackResponseSchema(TypedDict): | None ) + +class GroupScope(TypedDict): + group_by: str + """ + Field path to group by, e.g. metadata.session_id + """ + idle_seconds: NotRequired[float] + """ + Optional: trigger after this many seconds of inactivity + """ + interval_seconds: NotRequired[float] + """ + Maximum time range to include when constructing a group + """ + max_traces: NotRequired[int] + """ + Maximum number of traces to include when constructing a group (default/max: 64) + """ + placement: Literal["first", "each"] + """ + Which trace or traces to write grouped scorer results to + """ + type: Literal["group"] + + Ids: TypeAlias = str | Sequence[str] """ Filter search results to a particular set of object IDs. To specify a list of IDs, include the query param multiple times @@ -293,6 +349,22 @@ class ObjectReferenceNullish(TypedDict): Filter search results to within a particular organization """ +Permission: TypeAlias = Literal[ + "create", + "read", + "update", + "delete", + "create_acls", + "read_acls", + "update_acls", + "delete_acls", +] +""" +Each permission permits a certain type of operation on an object in the system + +Permissions can be assigned to to objects on an individual basis, or grouped into roles +""" + class PreprocessorId1(TypedDict): id: str @@ -463,6 +535,11 @@ class ResponseFormatNullish3(TypedDict): ResponseFormatNullish: TypeAlias = ResponseFormatNullish1 | ResponseFormatNullish2 | ResponseFormatNullish3 | None +RetentionObjectType: TypeAlias = Literal["project_logs", "experiment", "dataset"] +""" +The object type that the retention policy applies to +""" + class SavedFunctionId1(TypedDict): id: str @@ -489,6 +566,11 @@ class SavedFunctionId2(TypedDict): Retrieve prompt with a specific slug """ + +class SpanScope(TypedDict): + type: Literal["span"] + + StartingAfter: TypeAlias = str """ Pagination cursor id. @@ -496,6 +578,15 @@ class SavedFunctionId2(TypedDict): For example, if the final item in the last page you fetched had an id of `foo`, pass `starting_after=foo` to fetch the next page. Note: you may only pass one of `starting_after` and `ending_before` """ + +class TraceScope(TypedDict): + idle_seconds: NotRequired[float] + """ + Consider trace complete after this many seconds of inactivity (default: 30) + """ + type: Literal["trace"] + + Version: TypeAlias = str """ Retrieve a snapshot of events from a past time @@ -522,6 +613,18 @@ class ChatCompletionMessageToolCall(TypedDict): type: Literal["function"] +class Config4(TypedDict): + event_type: Literal["retention"] + """ + The type of automation. + """ + object_type: RetentionObjectType + retention_days: int + """ + The number of days to retain the object + """ + + class Classification(TypedDict): confidence: NotRequired[float | None] """ diff --git a/py/src/braintrust/api/_generated/models/dataset_snapshots.py b/py/src/braintrust/api/_generated/models/dataset_snapshots.py new file mode 100644 index 00000000..2c6dad5c --- /dev/null +++ b/py/src/braintrust/api/_generated/models/dataset_snapshots.py @@ -0,0 +1,83 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 1c8df3e63960997b01ee3f80bb76cf10713024534d5003690cb22b6fadb28545 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateDatasetSnapshot(TypedDict): + dataset_id: str + """ + Unique identifier for the dataset that this snapshot belongs to + """ + description: NotRequired[str | None] + """ + Textual description of the dataset snapshot + """ + name: str + """ + Name of the dataset snapshot + """ + xact_id: str + """ + Transaction id of the brainstore version at the time of the snapshot + """ + + +class DatasetSnapshot(TypedDict): + created: str | None + """ + Date of dataset snapshot creation + """ + dataset_id: str + """ + Unique identifier for the dataset that this snapshot belongs to + """ + description: str | None + id: str + """ + Unique identifier for the dataset snapshot + """ + name: str + """ + Name of the dataset snapshot + """ + xact_id: str + """ + Transaction id of the brainstore version at the time of the snapshot + """ + + +DatasetSnapshotIdParam: TypeAlias = str +""" +DatasetSnapshot id +""" + +DatasetSnapshotName: TypeAlias = str +""" +Name of the dataset_snapshot to search for +""" + + +class GetDatasetSnapshotResponse(TypedDict): + objects: Sequence[DatasetSnapshot] + """ + A list of dataset_snapshot objects + """ + + +class PatchDatasetSnapshot(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the dataset snapshot + """ + name: NotRequired[str | None] + """ + Name of the dataset snapshot + """ diff --git a/py/src/braintrust/api/_generated/models/datasets.py b/py/src/braintrust/api/_generated/models/datasets.py index 4f430aa6..e267c3f6 100644 --- a/py/src/braintrust/api/_generated/models/datasets.py +++ b/py/src/braintrust/api/_generated/models/datasets.py @@ -4,10 +4,10 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: fe1bb0191579f947158765086824d9cae75fd00b00e19be91e64fb89914ea3ae +# Content SHA-256: cc58640b43335aa96cdce169d657c0c508e19c285568b1622854013dc7cddedd -from typing import Any, Literal, TypeAlias, TypedDict from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict from collections.abc import Mapping, Sequence from .common import Classification, FieldArrayDeleteItem, Metadata, ObjectReferenceNullish diff --git a/py/src/braintrust/api/_generated/models/env_vars.py b/py/src/braintrust/api/_generated/models/env_vars.py new file mode 100644 index 00000000..3bfe423c --- /dev/null +++ b/py/src/braintrust/api/_generated/models/env_vars.py @@ -0,0 +1,90 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: ba9fb3e88d4db738617c2bb521c2b0942619e1cb3a940b466c0290cf8fa5503a + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class EnvVar(TypedDict): + created: NotRequired[str | None] + """ + Date of environment variable creation + """ + id: str + """ + Unique identifier for the environment variable + """ + metadata: NotRequired[Mapping[str, Any] | None] + """ + Optional metadata associated with the environment variable when managed via the function secrets API + """ + name: str + """ + The name of the environment variable + """ + object_id: str + """ + The id of the object the environment variable is scoped for + """ + object_type: Literal["organization", "project", "function"] + """ + The type of the object the environment variable is scoped for + """ + preview_secret: NotRequired[str | None] + """ + Redacted preview of the stored secret value + """ + secret_category: NotRequired[Literal["env_var", "ai_provider", "sandbox_provider"]] + """ + The category of the secret: env_var for regular environment variables, ai_provider for AI provider API keys + """ + secret_type: NotRequired[str | None] + """ + Optional classification for the secret (for example, the AI provider name) + """ + secret_updated_at: NotRequired[str | None] + """ + Date of last update to the encrypted secret value itself + """ + secret_updated_by_user_id: NotRequired[str | None] + """ + User id of the last update to the encrypted secret value + """ + used: NotRequired[str | None] + """ + Date the environment variable was last used + """ + + +EnvVarIdParam: TypeAlias = str +""" +EnvVar id +""" + +EnvVarName: TypeAlias = str +""" +Name of the env_var to search for +""" + +EnvVarObjectId: TypeAlias = str +""" +The id of the object the environment variable is scoped for +""" + +EnvVarObjectType: TypeAlias = Literal["organization", "project", "function"] +""" +The type of the object the environment variable is scoped for +""" + + +class GetEnvVarResponse(TypedDict): + objects: Sequence[EnvVar] + """ + A list of env_var objects + """ diff --git a/py/src/braintrust/api/_generated/models/environments.py b/py/src/braintrust/api/_generated/models/environments.py new file mode 100644 index 00000000..f8ca2dba --- /dev/null +++ b/py/src/braintrust/api/_generated/models/environments.py @@ -0,0 +1,80 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 977acecd3d3b6bf063a18a966522e55dd190fa6e1d4e52130ee8761a200b2c3b + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateEnvironment(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the environment + """ + name: str + """ + Name of the environment + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, you may specify the name of the organization the environment belongs in. + """ + slug: str + """ + A url-friendly, unique identifier for the environment within an organization + """ + + +class Environment(TypedDict): + created: NotRequired[str | None] + """ + Date of environment creation + """ + deleted_at: NotRequired[str | None] + """ + Date of environment deletion, or null if the environment is still active + """ + description: NotRequired[str | None] + """ + Textual description of the environment + """ + id: str + """ + Unique identifier for the environment + """ + name: str + """ + Name of the environment + """ + org_id: str + """ + Unique identifier for the organization that the environment belongs under + """ + slug: str + """ + A url-friendly, unique identifier for the environment within an organization + """ + + +class ListEnvironmentsResponse(TypedDict): + objects: Sequence[Environment] + + +class PatchEnvironment(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the environment + """ + name: NotRequired[str | None] + """ + Name of the environment + """ + slug: NotRequired[str | None] + """ + A url-friendly, unique identifier for the environment within an organization + """ diff --git a/py/src/braintrust/api/_generated/models/experiments.py b/py/src/braintrust/api/_generated/models/experiments.py index a295f24e..e358bf5a 100644 --- a/py/src/braintrust/api/_generated/models/experiments.py +++ b/py/src/braintrust/api/_generated/models/experiments.py @@ -4,10 +4,10 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: 48aad16184321bf01b7d94dbc3515e18099eeccac91c32dae3c2c5a09af5e38d +# Content SHA-256: e555f7b4a51d283b283592e74e662ddad710e8946629721a5c74dd9cd8a0246b -from typing import Any, Literal, TypeAlias, TypedDict from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict from collections.abc import Mapping, Sequence from .common import Classification, FieldArrayDeleteItem, Metadata, ObjectReferenceNullish diff --git a/py/src/braintrust/api/_generated/models/functions.py b/py/src/braintrust/api/_generated/models/functions.py index 6c0fd634..3f92e238 100644 --- a/py/src/braintrust/api/_generated/models/functions.py +++ b/py/src/braintrust/api/_generated/models/functions.py @@ -4,34 +4,19 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: 8f75af48b162ca2c86ab19fb6a4850e12eac3b9e2376887cad73109e42c833e5 +# Content SHA-256: 04002c71fe10c51256fdf3fadcd561b422c1c63cd5674c4dd626311b12eed113 -from typing import Any, Literal, TypeAlias, TypedDict from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict from collections.abc import Mapping, Sequence -from .common import ChatCompletionMessageParam, FunctionTypeEnum, FunctionTypeEnumNullish, PromptDataNullish - -AclObjectType: TypeAlias = Literal[ - "organization", - "project", - "experiment", - "dataset", - "prompt", - "prompt_session", - "group", - "role", - "org_member", - "project_log", - "org_project", - "org_audit_logs", - "project_group", - "ai_secret", - "org_ai_secret", -] -""" -The object type that the ACL applies to -""" +from .common import ( + AclObjectType, + ChatCompletionMessageParam, + FunctionTypeEnum, + FunctionTypeEnumNullish, + PromptDataNullish, +) class Facet(TypedDict): diff --git a/py/src/braintrust/api/_generated/models/groups.py b/py/src/braintrust/api/_generated/models/groups.py new file mode 100644 index 00000000..74107b97 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/groups.py @@ -0,0 +1,124 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 96dc21bc161bc5a0f33ccdd9b5e660ed251030c4b7f59a59563297b101ae4193 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateGroup(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the group + """ + member_groups: NotRequired[Sequence[str] | None] + """ + Ids of the groups this group inherits from + + An inheriting group has all the users contained in its member groups, as well as all of their inherited users + """ + member_users: NotRequired[Sequence[str] | None] + """ + Ids of users which belong to this group + """ + name: str + """ + Name of the group + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, you may specify the name of the organization the group belongs in. + """ + + +class Group(TypedDict): + created: NotRequired[str | None] + """ + Date of group creation + """ + deleted_at: NotRequired[str | None] + """ + Date of group deletion, or null if the group is still active + """ + description: NotRequired[str | None] + """ + Textual description of the group + """ + id: str + """ + Unique identifier for the group + """ + member_groups: NotRequired[Sequence[str] | None] + """ + Ids of the groups this group inherits from + + An inheriting group has all the users contained in its member groups, as well as all of their inherited users + """ + member_users: NotRequired[Sequence[str] | None] + """ + Ids of users which belong to this group + """ + name: str + """ + Name of the group + """ + org_id: str + """ + Unique id for the organization that the group belongs under + + It is forbidden to change the org after creating a group + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the group + """ + + +GroupIdParam: TypeAlias = str +""" +Group id +""" + +GroupName: TypeAlias = str +""" +Name of the group to search for +""" + + +class PatchGroup(TypedDict): + add_member_groups: NotRequired[Sequence[str] | None] + """ + A list of group IDs to add to the group's inheriting-from set + """ + add_member_users: NotRequired[Sequence[str] | None] + """ + A list of user IDs to add to the group + """ + description: NotRequired[str | None] + """ + Textual description of the group + """ + name: NotRequired[str | None] + """ + Name of the group + """ + remove_member_groups: NotRequired[Sequence[str] | None] + """ + A list of group IDs to remove from the group's inheriting-from set + """ + remove_member_users: NotRequired[Sequence[str] | None] + """ + A list of user IDs to remove from the group + """ + + +class GetGroupResponse(TypedDict): + objects: Sequence[Group] + """ + A list of group objects + """ diff --git a/py/src/braintrust/api/_generated/models/mcp_servers.py b/py/src/braintrust/api/_generated/models/mcp_servers.py new file mode 100644 index 00000000..f295f964 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/mcp_servers.py @@ -0,0 +1,98 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: b23ca69ae5400afdc852a1fa6fc61329bb4a1cf34d6de4af08098505184d46c7 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateMCPServer(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the MCP server + """ + name: str + """ + Name of the MCP server. Within a project, MCP server names are unique + """ + project_id: str + """ + Unique identifier for the project that the MCP server belongs under + """ + url: str + """ + URL of the MCP server endpoint + """ + + +class MCPServer(TypedDict): + created: NotRequired[str | None] + """ + Date of MCP server creation + """ + deleted_at: NotRequired[str | None] + """ + Date of MCP server deletion, or null if the MCP server is still active + """ + description: NotRequired[str | None] + """ + Textual description of the MCP server + """ + id: str + """ + Unique identifier for the MCP server + """ + name: str + """ + Name of the MCP server. Within a project, MCP server names are unique + """ + project_id: str + """ + Unique identifier for the project that the MCP server belongs under + """ + url: str + """ + URL of the MCP server endpoint + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the MCP server + """ + + +McpServerIdParam: TypeAlias = str +""" +McpServer id +""" + +McpServerName: TypeAlias = str +""" +Name of the mcp_server to search for +""" + + +class PatchMCPServer(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the MCP server + """ + name: NotRequired[str | None] + """ + Name of the MCP server. Within a project, MCP server names are unique + """ + url: NotRequired[str | None] + """ + URL of the MCP server endpoint + """ + + +class GetMcpServerResponse(TypedDict): + objects: Sequence[MCPServer] + """ + A list of mcp_server objects + """ diff --git a/py/src/braintrust/api/_generated/models/org_automations.py b/py/src/braintrust/api/_generated/models/org_automations.py new file mode 100644 index 00000000..f5c603a8 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/org_automations.py @@ -0,0 +1,107 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: d1615033766f1c4d0f8e47933f414909e88c91e3e031941f616bd50c17b4988d + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +from .common import Config4, RetentionObjectType + +OrgAutomationIdParam: TypeAlias = str +""" +OrgAutomation id +""" + +OrgAutomationName: TypeAlias = str +""" +Name of the org_automation to search for +""" + + +class Config(TypedDict): + event_type: Literal["retention"] + """ + The type of automation. + """ + object_type: RetentionObjectType + retention_days: int + """ + The number of days to retain the object + """ + + +class CreateOrgAutomation(TypedDict): + config: Config + """ + The configuration for the org automation rule + """ + description: NotRequired[str | None] + """ + Textual description of the project automation + """ + name: str + """ + Name of the project automation + """ + org_id: str + """ + Unique identifier for the organization that the org automation belongs under + """ + + +class OrgAutomation(TypedDict): + config: Config4 + """ + The configuration for the org automation rule + """ + created: NotRequired[str | None] + """ + Date of project automation creation + """ + description: NotRequired[str | None] + """ + Textual description of the project automation + """ + id: str + """ + Unique identifier for the project automation + """ + name: str + """ + Name of the project automation + """ + org_id: str + """ + Unique identifier for the organization that the org automation belongs under + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the project automation + """ + + +class PatchOrgAutomation(TypedDict): + config: NotRequired[Config4 | None] + """ + The configuration for the org automation rule + """ + description: NotRequired[str | None] + """ + Textual description of the project automation + """ + name: NotRequired[str | None] + """ + Name of the project automation + """ + + +class GetOrgAutomationResponse(TypedDict): + objects: Sequence[OrgAutomation] + """ + A list of org_automation objects + """ diff --git a/py/src/braintrust/api/_generated/models/organizations.py b/py/src/braintrust/api/_generated/models/organizations.py new file mode 100644 index 00000000..8efce0eb --- /dev/null +++ b/py/src/braintrust/api/_generated/models/organizations.py @@ -0,0 +1,163 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 8757f0faef2942e8e06cb708ffb9676058767f9cc305bf1cde5d49a3e8905f71 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +ImageRenderingMode: TypeAlias = Literal["auto", "click_to_load", "blocked"] | None +""" +Controls how images are rendered in the UI: 'auto' loads images automatically, 'click_to_load' shows a placeholder until clicked, 'blocked' prevents image loading entirely +""" + + +class Organization(TypedDict): + api_url: NotRequired[str | None] + created: NotRequired[str | None] + """ + Date of organization creation + """ + id: str + """ + Unique identifier for the organization + """ + image_rendering_mode: NotRequired[ImageRenderingMode | None] + is_dataplane_private: NotRequired[bool | None] + is_universal_api: NotRequired[bool | None] + name: str + """ + Name of the organization + """ + proxy_url: NotRequired[str | None] + realtime_url: NotRequired[str | None] + + +OrganizationIdParam: TypeAlias = str +""" +Organization id +""" + + +class PatchOrganization(TypedDict): + api_url: NotRequired[str | None] + image_rendering_mode: NotRequired[ImageRenderingMode | None] + is_dataplane_private: NotRequired[bool | None] + is_universal_api: NotRequired[bool | None] + name: NotRequired[str | None] + """ + Name of the organization + """ + proxy_url: NotRequired[str | None] + realtime_url: NotRequired[str | None] + + +class ServiceAccount(TypedDict): + name: str + token_expires_in_seconds: NotRequired[int | None] + """ + Number of seconds from now after which the initial service token should expire. If omitted, the token never expires. + """ + token_name: NotRequired[str | None] + """ + Optional name of an initial service token to create for the new service account. This is a narrow compatibility carve-out only on PATCH /v1/organization/members. When this field is set, the request must be authenticated with a service token that has organization-owner permissions, not a user API key. + """ + + +class InviteUsers(TypedDict): + emails: NotRequired[Sequence[str] | None] + """ + Emails of users to invite + """ + group_id: NotRequired[str | None] + """ + Singular form of group_ids + """ + group_ids: NotRequired[Sequence[str] | None] + """ + Optional list of group ids to add newly-invited users to. + """ + group_name: NotRequired[str | None] + """ + Singular form of group_names + """ + group_names: NotRequired[Sequence[str] | None] + """ + Optional list of group names to add newly-invited users to. + """ + ids: NotRequired[Sequence[str] | None] + """ + Ids of existing users to invite + """ + send_invite_emails: NotRequired[bool | None] + """ + If true, send invite emails to the users who wore actually added + """ + service_accounts: NotRequired[Sequence[ServiceAccount] | None] + """ + Service accounts to create. PATCH /v1/organization/members is the compatibility layer that accepts both plain service-account creation and the narrower token_name create-and-mint carve-out. + """ + + +class RemoveUsers(TypedDict): + emails: NotRequired[Sequence[str] | None] + """ + Emails of users to remove + """ + ids: NotRequired[Sequence[str] | None] + """ + Ids of users to remove + """ + + +class PatchOrganizationMembers(TypedDict): + invite_users: NotRequired[InviteUsers | None] + """ + Users to invite to the organization + """ + org_id: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, or in case you want to explicitly assert the organization you are modifying, you may specify the id of the organization. + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, or in case you want to explicitly assert the organization you are modifying, you may specify the name of the organization. + """ + remove_users: NotRequired[RemoveUsers | None] + """ + Users to remove from the organization + """ + + +class AddedUser(TypedDict): + api_key: NotRequired[str | None] + email: NotRequired[str | None] + id: str + token_name: NotRequired[str | None] + + +class PatchOrganizationMembersOutput(TypedDict): + added_users: NotRequired[Sequence[AddedUser] | None] + """ + The users who were added by this request. api_key and token_name are only present for the inline service-account create-and-mint compatibility path. + """ + org_id: str + """ + The id of the org that was modified. + """ + send_email_error: NotRequired[str | None] + """ + If invite emails failed to send for some reason, the patch operation will still complete, but we will return an error message here + """ + status: Literal["success"] + + +class GetOrganizationResponse(TypedDict): + objects: Sequence[Organization] + """ + A list of organization objects + """ diff --git a/py/src/braintrust/api/_generated/models/project_automations.py b/py/src/braintrust/api/_generated/models/project_automations.py new file mode 100644 index 00000000..99ee243e --- /dev/null +++ b/py/src/braintrust/api/_generated/models/project_automations.py @@ -0,0 +1,1209 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 2077728154fbe14ee870e894e3289a02099bad4e29d3562bd37e3208476dd7c8 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +from .common import AutomationStatus, Config4, FunctionTypeEnum, GroupScope, RetentionObjectType, SpanScope, TraceScope + + +class Action(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Action1(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Config1(TypedDict): + action: Action | Action1 + """ + The action to take when the automation rule is triggered + """ + btql_filter: str + """ + BTQL filter to identify rows for the automation rule + """ + event_type: Literal["logs"] + """ + The type of automation. + """ + interval_seconds: float + """ + Perform the triggered action at most once in this interval of seconds + """ + status: NotRequired[AutomationStatus] + + +class Credentials(TypedDict): + external_id: str + """ + The automation-specific external id component (auto-generated by default) + """ + role_arn: str + """ + The ARN of the IAM role to use + """ + type: Literal["aws_iam"] + + +class Credentials1(TypedDict): + service_account_email: str + """ + The GCP service account email to impersonate + """ + type: Literal["gcp_service_account"] + + +class ExportDefinition(TypedDict): + type: Literal["log_traces"] + + +class ExportDefinition1(TypedDict): + type: Literal["log_spans"] + + +class ExportDefinition2(TypedDict): + btql_query: str + """ + The BTQL query to export + """ + type: Literal["btql_query"] + + +class Config3(TypedDict): + batch_size: NotRequired[int | None] + """ + The maximum number of result rows to write per async query batch + """ + created_by_user_id: str + """ + The user who submitted the async query + """ + event_type: Literal["async_query"] + """ + The type of automation. + """ + format: Literal["jsonl"] + """ + The materialized result format + """ + object_id: str + """ + The source object ID for the async query + """ + object_type: Literal["project_logs", "experiment", "dataset", "playground_logs"] + """ + The source object type for the async query + """ + query: str + """ + The SQL query to execute asynchronously + """ + status: NotRequired[AutomationStatus] + + +class Action2(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Action3(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Config5(TypedDict): + action: Action2 | Action3 + """ + The action to take when the automation rule is triggered + """ + environment_filter: NotRequired[Sequence[str]] + """ + Optional list of environment slugs to filter by + """ + event_type: Literal["environment_update"] + """ + The type of automation. + """ + status: NotRequired[AutomationStatus] + + +class Action4(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Action5(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Config8(TypedDict): + action: Action4 | Action5 + """ + The action to take when the automation rule is triggered + """ + btql_filter: str + """ + BTQL filter to identify rows for the automation rule + """ + event_type: Literal["logs"] + """ + The type of automation. + """ + interval_seconds: float + """ + Perform the triggered action at most once in this interval of seconds + """ + status: NotRequired[AutomationStatus] + + +class Credentials2(TypedDict): + external_id: str + """ + The automation-specific external id component (auto-generated by default) + """ + role_arn: str + """ + The ARN of the IAM role to use + """ + type: Literal["aws_iam"] + + +class Credentials3(TypedDict): + service_account_email: str + """ + The GCP service account email to impersonate + """ + type: Literal["gcp_service_account"] + + +class ExportDefinition3(TypedDict): + type: Literal["log_traces"] + + +class ExportDefinition4(TypedDict): + type: Literal["log_spans"] + + +class ExportDefinition5(TypedDict): + btql_query: str + """ + The BTQL query to export + """ + type: Literal["btql_query"] + + +class Config10(TypedDict): + batch_size: NotRequired[int | None] + """ + The maximum number of result rows to write per async query batch + """ + created_by_user_id: str + """ + The user who submitted the async query + """ + event_type: Literal["async_query"] + """ + The type of automation. + """ + format: Literal["jsonl"] + """ + The materialized result format + """ + object_id: str + """ + The source object ID for the async query + """ + object_type: Literal["project_logs", "experiment", "dataset", "playground_logs"] + """ + The source object type for the async query + """ + query: str + """ + The SQL query to execute asynchronously + """ + status: NotRequired[AutomationStatus] + + +class Action6(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Action7(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Config12(TypedDict): + action: Action6 | Action7 + """ + The action to take when the automation rule is triggered + """ + environment_filter: NotRequired[Sequence[str]] + """ + Optional list of environment slugs to filter by + """ + event_type: Literal["environment_update"] + """ + The type of automation. + """ + status: NotRequired[AutomationStatus] + + +class Action8(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Action9(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Config13(TypedDict): + action: Action8 | Action9 + """ + The action to take when the automation rule is triggered + """ + btql_filter: str + """ + BTQL filter to identify rows for the automation rule + """ + event_type: Literal["logs"] + """ + The type of automation. + """ + interval_seconds: float + """ + Perform the triggered action at most once in this interval of seconds + """ + status: NotRequired[AutomationStatus] + + +class Credentials4(TypedDict): + external_id: str + """ + The automation-specific external id component (auto-generated by default) + """ + role_arn: str + """ + The ARN of the IAM role to use + """ + type: Literal["aws_iam"] + + +class Credentials5(TypedDict): + service_account_email: str + """ + The GCP service account email to impersonate + """ + type: Literal["gcp_service_account"] + + +class ExportDefinition6(TypedDict): + type: Literal["log_traces"] + + +class ExportDefinition7(TypedDict): + type: Literal["log_spans"] + + +class ExportDefinition8(TypedDict): + btql_query: str + """ + The BTQL query to export + """ + type: Literal["btql_query"] + + +class Config15(TypedDict): + batch_size: NotRequired[int | None] + """ + The maximum number of result rows to write per async query batch + """ + created_by_user_id: str + """ + The user who submitted the async query + """ + event_type: Literal["async_query"] + """ + The type of automation. + """ + format: Literal["jsonl"] + """ + The materialized result format + """ + object_id: str + """ + The source object ID for the async query + """ + object_type: Literal["project_logs", "experiment", "dataset", "playground_logs"] + """ + The source object type for the async query + """ + query: str + """ + The SQL query to execute asynchronously + """ + status: NotRequired[AutomationStatus] + + +class Action10(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Action11(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Config17(TypedDict): + action: Action10 | Action11 + """ + The action to take when the automation rule is triggered + """ + environment_filter: NotRequired[Sequence[str]] + """ + Optional list of environment slugs to filter by + """ + event_type: Literal["environment_update"] + """ + The type of automation. + """ + status: NotRequired[AutomationStatus] + + +ProjectAutomationIdParam: TypeAlias = str +""" +ProjectAutomation id +""" + +ProjectAutomationName: TypeAlias = str +""" +Name of the project_automation to search for +""" + +BackfillTimeRange = TypedDict( + "BackfillTimeRange", + { + "from": str, + "to": str, + }, +) + + +class FacetFunction1(TypedDict): + id: str + type: Literal["function"] + version: NotRequired[str] + """ + The version of the function + """ + + +class FacetFunction2(TypedDict): + function_type: NotRequired[FunctionTypeEnum] + name: str + type: Literal["global"] + + +class FacetFunction3(TypedDict): + pass + + +class FacetFunction4(FacetFunction1, FacetFunction3): + pass + + +class FacetFunction5(FacetFunction2, FacetFunction3): + pass + + +class FacetFunction6(FacetFunction1, FacetFunction3): + pass + + +class FacetFunction7(FacetFunction2, FacetFunction3): + pass + + +FacetFunction: TypeAlias = FacetFunction4 | FacetFunction5 | FacetFunction6 | FacetFunction7 + + +class TopicAutomationDataScope1(TypedDict): + type: Literal["project_logs"] + + +class TopicAutomationDataScope2(TypedDict): + type: Literal["project_experiments"] + + +class TopicAutomationDataScope3(TypedDict): + experiment_id: str + type: Literal["experiment"] + + +TopicAutomationDataScope: TypeAlias = ( + TopicAutomationDataScope1 | TopicAutomationDataScope2 | TopicAutomationDataScope3 | None +) +""" +Optional data scope for topic automation. +""" + +TopicAutomationFacetModel: TypeAlias = Literal["brain-facet-latest", "brain-facet-1", "brain-facet-2"] | None +""" +Optional facet model override for topic automation +""" + + +class TopicDigestAutomationConfig(TypedDict): + action: Action11 + """ + The Slack action to take when the digest is sent + """ + event_type: Literal["topic_digest"] + """ + The type of automation. + """ + scheduled_time_minutes_utc: int + """ + Minutes after midnight UTC when the digest should be sent + """ + status: NotRequired[AutomationStatus] + topic_map_function_ids: NotRequired[Sequence[str]] + """ + Optional topic map function IDs to include in the digest + """ + window_seconds: NotRequired[int] + """ + How much recent history to include in each digest + """ + + +class Function11(TypedDict): + id: str + type: Literal["function"] + version: NotRequired[str] + """ + The version of the function + """ + + +class Function12(TypedDict): + function_type: NotRequired[FunctionTypeEnum] + name: str + type: Literal["global"] + + +class Function13(TypedDict): + pass + + +class Function14(Function11, Function13): + pass + + +class Function15(Function12, Function13): + pass + + +class Function16(Function11, Function13): + pass + + +class Function17(Function12, Function13): + pass + + +Function1: TypeAlias = Function14 | Function15 | Function16 | Function17 + + +class TopicMapFunctionAutomation(TypedDict): + btql_filter: NotRequired[str | None] + """ + Per-topic-map BTQL filter. For trace scope, a topic map runs when max(filter) over the trace is truthy. For span scope, it runs when the current span matches. + """ + function: Function1 + + +class Actions(TypedDict): + formatting_prompt: NotRequired[str] + """ + Instructions for Loop to format content sent to this destination + """ + type: Literal["webhook"] + """ + The type of action to take + """ + url: str + """ + The webhook URL to send the request to + """ + + +class Actions1(TypedDict): + channel: str + """ + The Slack channel ID to post to + """ + formatting_prompt: NotRequired[str] + """ + Publish a Slack mrkdwn digest. + + Include a complete "*Pattern outcomes*" section with one row for every selected Pattern from the run report, including created, updated, unchanged, failed, skipped, and newly inactive outcomes. + + Use this row format exactly: + • — `outcome` + + If a Pattern has no URL, use the plain title instead. Do not use GitHub Markdown tables or code-block tables, because links must remain clickable. Do not omit any selected Pattern. If there are no selected Patterns, say "No pattern outcomes." + + After the outcome list, include a "*Highlights*" section with one very short paragraph, 2-3 sentences maximum. Summarize what changed or what broadly stands out from this run. Do not introduce new claims beyond the run report. + """ + message_template: NotRequired[str] + """ + Custom message template for the alert + """ + type: Literal["slack"] + """ + The type of action to take + """ + workspace_id: str + """ + The Slack workspace ID to post to + """ + + +class Loop(TypedDict): + agent_slug: str + """ + The Loop agent to run + """ + auto_approve_tools: NotRequired[Sequence[str]] + """ + Write tools that may run without interactive approval + """ + endpoint_name: NotRequired[str] + harness: NotRequired[Literal["native", "codex", "claude-code"]] + include_trigger_input: NotRequired[bool] + """ + Whether to include the automation trigger payload as input + """ + model: NotRequired[str] + prompt: str + """ + Instructions for the Loop agent + """ + reasoning_effort: NotRequired[Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]] + + +class Output(TypedDict): + type: Literal["scalar"] + value_column: str + """ + The numeric result column produced by the query + """ + + +class Calculation(TypedDict): + btql_query: str + """ + A project-scoped BTQL or SQL query without runtime-owned evaluation time bounds + """ + output: Output + type: Literal["btql"] + + +class Condition(TypedDict): + operator: Literal["lt", "lte", "gt", "gte", "eq", "neq"] + threshold: float + type: Literal["threshold"] + + +class Policy(TypedDict): + condition: Condition + no_data_behavior: Literal["keep_last", "resolve", "alert"] + """ + How the lifecycle changes when the calculation returns no data + """ + notify_on_recovery: NotRequired[bool] + """ + Whether to deliver actions when a firing automation recovers + """ + pending_seconds: int + """ + How long the condition must remain breached before firing + """ + renotify_interval_seconds: NotRequired[int | None] + """ + Optional reminder interval while the automation is firing + """ + + +class Threshold(TypedDict): + calculation: Calculation + """ + The calculation evaluated for each window + """ + policy: Policy + """ + The lifecycle policy applied to each calculation result + """ + + +class Schedule(TypedDict): + evaluation_interval_seconds: int + """ + How often the automation runs + """ + type: Literal["interval"] + + +class Schedule1(TypedDict): + cron_expression: str + """ + A standard five-field cron expression (minute hour day-of-month month day-of-week) controlling when the automation runs + """ + timezone: NotRequired[str | None] + """ + IANA timezone used to interpret the cron expression (defaults to UTC) + """ + type: Literal["cron"] + + +class Window(TypedDict): + evaluation_delay_seconds: int + """ + How far behind the present each evaluation window ends + """ + schedule: Schedule | Schedule1 + """ + How often the windowed automation runs: at a fixed interval or on a cron schedule + """ + window_seconds: int + """ + How much recent data each scheduled run covers + """ + + +class WindowedAutomationConfig(TypedDict): + actions: NotRequired[Sequence[Actions | Actions1]] + """ + Delivery actions exposed to Loop as tools, or run directly when Loop is not configured + """ + event_type: Literal["windowed"] + """ + The type of automation. + """ + loop: NotRequired[Loop] + """ + Optional Loop agent to run for each triggered window + """ + product_origin: NotRequired[Literal["patterns"] | None] + """ + The product surface that created and manages the automation + """ + status: NotRequired[AutomationStatus] + threshold: NotRequired[Threshold] + """ + Optional calculation and lifecycle policy that gate scheduled delivery + """ + window: Window + + +class Config2(TypedDict): + batch_size: NotRequired[float | None] + """ + The number of rows to export in each batch + """ + credentials: Credentials | Credentials1 + event_type: Literal["btql_export"] + """ + The type of automation. + """ + export_definition: ExportDefinition | ExportDefinition1 | ExportDefinition2 + """ + The definition of what to export + """ + export_path: str + """ + The path to export the results to. It should include the storage protocol and prefix, e.g. s3://bucket-name/path/to/export + """ + format: Literal["jsonl", "parquet"] + """ + The format to export the results in + """ + interval_seconds: float + """ + Perform the triggered action at most once in this interval of seconds + """ + scope: NotRequired[SpanScope | TraceScope | GroupScope | None] + """ + Execution scope for export automation. Defaults to span-level execution. + """ + status: NotRequired[AutomationStatus] + + +class Config9(TypedDict): + batch_size: NotRequired[float | None] + """ + The number of rows to export in each batch + """ + credentials: Credentials2 | Credentials3 + event_type: Literal["btql_export"] + """ + The type of automation. + """ + export_definition: ExportDefinition3 | ExportDefinition4 | ExportDefinition5 + """ + The definition of what to export + """ + export_path: str + """ + The path to export the results to. It should include the storage protocol and prefix, e.g. s3://bucket-name/path/to/export + """ + format: Literal["jsonl", "parquet"] + """ + The format to export the results in + """ + interval_seconds: float + """ + Perform the triggered action at most once in this interval of seconds + """ + scope: NotRequired[SpanScope | TraceScope | GroupScope | None] + """ + Execution scope for export automation. Defaults to span-level execution. + """ + status: NotRequired[AutomationStatus] + + +class Config11(TypedDict): + event_type: Literal["retention"] + """ + The type of automation. + """ + object_type: RetentionObjectType + retention_days: int + """ + The number of days to retain the object + """ + + +class Config14(TypedDict): + batch_size: NotRequired[float | None] + """ + The number of rows to export in each batch + """ + credentials: Credentials4 | Credentials5 + event_type: Literal["btql_export"] + """ + The type of automation. + """ + export_definition: ExportDefinition6 | ExportDefinition7 | ExportDefinition8 + """ + The definition of what to export + """ + export_path: str + """ + The path to export the results to. It should include the storage protocol and prefix, e.g. s3://bucket-name/path/to/export + """ + format: Literal["jsonl", "parquet"] + """ + The format to export the results in + """ + interval_seconds: float + """ + Perform the triggered action at most once in this interval of seconds + """ + scope: NotRequired[SpanScope | TraceScope | GroupScope | None] + """ + Execution scope for export automation. Defaults to span-level execution. + """ + status: NotRequired[AutomationStatus] + + +class Config16(TypedDict): + event_type: Literal["retention"] + """ + The type of automation. + """ + object_type: RetentionObjectType + retention_days: int + """ + The number of days to retain the object + """ + + +class TopicAutomationConfig(TypedDict): + backfill_time_range: NotRequired[str | BackfillTimeRange | None] + """ + Topic window used for classification coverage and initial backfill. + """ + btql_filter: NotRequired[str | None] + """ + Optional BTQL filter applied before topic automation. + """ + data_scope: NotRequired[TopicAutomationDataScope] + event_type: Literal["topic"] + """ + The type of automation. + """ + facet_functions: Sequence[FacetFunction] + """ + Facet functions used by the topic automation + """ + facet_model: NotRequired[TopicAutomationFacetModel | None] + relabel_overlap_seconds: NotRequired[float | None] + """ + How much recent history to relabel after a new topic map version becomes active + """ + rerun_seconds: NotRequired[float | None] + """ + How often to recompute topic maps + """ + sampling_rate: float + """ + The sampling rate for topic automation + """ + scope: NotRequired[SpanScope | TraceScope | GroupScope | None] + """ + Execution scope for topic automation. + """ + status: NotRequired[AutomationStatus] + topic_map_functions: Sequence[TopicMapFunctionAutomation] + """ + Topic map functions with optional per-topic-map filters + """ + + +class CreateProjectAutomation(TypedDict): + config: ( + Config1 + | Config2 + | Config3 + | Config4 + | Config5 + | WindowedAutomationConfig + | TopicAutomationConfig + | TopicDigestAutomationConfig + ) + """ + The configuration for the automation rule + """ + description: NotRequired[str | None] + """ + Textual description of the project automation + """ + name: str + """ + Name of the project automation + """ + project_id: str + """ + Unique identifier for the project that the project automation belongs under + """ + + +class PatchProjectAutomation(TypedDict): + config: NotRequired[ + Config8 + | Config9 + | Config10 + | Config11 + | Config12 + | WindowedAutomationConfig + | TopicAutomationConfig + | TopicDigestAutomationConfig + | Any + ] + """ + The configuration for the automation rule + """ + description: NotRequired[str | None] + """ + Textual description of the project automation + """ + name: NotRequired[str | None] + """ + Name of the project automation + """ + + +class ProjectAutomation(TypedDict): + config: ( + Config13 + | Config14 + | Config15 + | Config16 + | Config17 + | WindowedAutomationConfig + | TopicAutomationConfig + | TopicDigestAutomationConfig + ) + """ + The configuration for the automation rule + """ + created: NotRequired[str | None] + """ + Date of project automation creation + """ + description: NotRequired[str | None] + """ + Textual description of the project automation + """ + id: str + """ + Unique identifier for the project automation + """ + name: str + """ + Name of the project automation + """ + project_id: str + """ + Unique identifier for the project that the project automation belongs under + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the project automation + """ + + +class GetProjectAutomationResponse(TypedDict): + objects: Sequence[ProjectAutomation] + """ + A list of project_automation objects + """ diff --git a/py/src/braintrust/api/_generated/models/project_groups.py b/py/src/braintrust/api/_generated/models/project_groups.py new file mode 100644 index 00000000..4231fd7d --- /dev/null +++ b/py/src/braintrust/api/_generated/models/project_groups.py @@ -0,0 +1,100 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: b640d57f8d16916eac9cbda4ac6d565638af2611ec914903004b88e105d25a53 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateProjectGroup(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the project group + """ + name: str + """ + Name of the project group + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, you may specify the name of the organization the project group belongs in. + """ + + +class PatchProjectGroup(TypedDict): + add_member_projects: NotRequired[Sequence[str] | None] + """ + A list of project IDs to add to the project group + """ + description: NotRequired[str | None] + """ + Textual description of the project group + """ + name: NotRequired[str | None] + """ + Name of the project group + """ + remove_member_projects: NotRequired[Sequence[str] | None] + """ + A list of project IDs to remove from the project group + """ + + +class ProjectGroup(TypedDict): + created: NotRequired[str | None] + """ + Date of project group creation + """ + deleted_at: NotRequired[str | None] + """ + Date of project group deletion, or null if the project group is still active + """ + description: NotRequired[str | None] + """ + Textual description of the project group + """ + id: str + """ + Unique identifier for the project group + """ + member_projects: Sequence[str] + """ + Sorted ids of active projects in this project group + """ + name: str + """ + Name of the project group + """ + org_id: str + """ + Unique id for the organization that the project group belongs under + + It is forbidden to change the org after creating a project group + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the project group + """ + + +ProjectGroupIdParam: TypeAlias = str +""" +ProjectGroup id +""" + +ProjectGroupName: TypeAlias = str +""" +Name of the project_group to search for +""" + + +class GetProjectGroupResponse(TypedDict): + objects: Sequence[ProjectGroup] + """ + A list of project_group objects + """ diff --git a/py/src/braintrust/api/_generated/models/project_scores.py b/py/src/braintrust/api/_generated/models/project_scores.py new file mode 100644 index 00000000..6f23e593 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/project_scores.py @@ -0,0 +1,208 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 8131225200448e09109b065a125dc7307509cb43906ef1de772f600610107ac2 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +from .common import AutomationStatus, FunctionTypeEnum, GroupScope, SpanScope, TraceScope + + +class Scorer1(TypedDict): + id: str + type: Literal["function"] + version: NotRequired[str] + """ + The version of the function + """ + + +class Scorer2(TypedDict): + function_type: NotRequired[FunctionTypeEnum] + name: str + type: Literal["global"] + + +class Scorer3(TypedDict): + pass + + +class Scorer4(Scorer1, Scorer3): + pass + + +class Scorer5(Scorer2, Scorer3): + pass + + +class Scorer6(Scorer1, Scorer3): + pass + + +class Scorer7(Scorer2, Scorer3): + pass + + +Scorer: TypeAlias = Scorer4 | Scorer5 | Scorer6 | Scorer7 + + +class ProjectScoreCategory(TypedDict): + name: str + """ + Name of the category + """ + value: float + """ + Numerical value of the category. Must be between 0 and 1, inclusive + """ + + +class When(TypedDict): + clauses: NotRequired[Sequence[str] | None] + subspan_clauses: NotRequired[Sequence[str] | None] + trace_clauses: NotRequired[Sequence[str] | None] + + +class ProjectScoreCondition(TypedDict): + behavior: NotRequired[Literal["hidden"]] + when: When + + +class Visibility(TypedDict): + groups: NotRequired[Sequence[str] | None] + users: NotRequired[Sequence[str] | None] + + +ProjectScoreIdParam: TypeAlias = str +""" +ProjectScore id +""" + +ProjectScoreName: TypeAlias = str +""" +Name of the project_score to search for +""" + +ProjectScoreType: TypeAlias = Literal["slider", "categorical", "weighted", "minimum", "maximum", "online", "free-form"] +""" +The type of the configured score +""" + + +class OnlineScoreConfig(TypedDict): + apply_to_root_span: NotRequired[bool | None] + """ + Whether to trigger online scoring on the root span of each trace. Only applies when scope is 'span' or unset. + """ + apply_to_span_names: NotRequired[Sequence[str] | None] + """ + Trigger online scoring on any spans with a name in this list. Only applies when scope is 'span' or unset. + """ + btql_filter: NotRequired[str | None] + """ + Filter logs using BTQL + """ + sampling_rate: float + """ + The sampling rate for online scoring + """ + scope: NotRequired[SpanScope | TraceScope | GroupScope | None] + """ + The scope at which to run the functions. Defaults to span-level execution. + """ + scorers: Sequence[Scorer] + """ + The list of functions to run for online scoring. Can include scorers, facets, or other function types. + """ + skip_logging: NotRequired[bool | None] + """ + Whether to skip adding scorer spans when computing scores + """ + status: NotRequired[AutomationStatus] + + +ProjectScoreCategories: TypeAlias = Sequence[ProjectScoreCategory] | Mapping[str, float] | Sequence[str] | None + + +class ProjectScoreConfig(TypedDict): + condition: NotRequired[ProjectScoreCondition | None] + destination: NotRequired[str | None] + multi_select: NotRequired[bool | None] + object_types: NotRequired[Sequence[Literal["project_logs", "dataset", "experiment"]] | None] + online: NotRequired[OnlineScoreConfig | None] + visibility: NotRequired[Visibility | None] + + +class CreateProjectScore(TypedDict): + categories: NotRequired[ProjectScoreCategories] + config: NotRequired[ProjectScoreConfig | None] + description: NotRequired[str | None] + """ + Textual description of the project score + """ + name: str + """ + Name of the project score + """ + project_id: str + """ + Unique identifier for the project that the project score belongs under + """ + score_type: ProjectScoreType + + +class PatchProjectScore(TypedDict): + categories: NotRequired[ProjectScoreCategories] + config: NotRequired[ProjectScoreConfig | None] + description: NotRequired[str | None] + """ + Textual description of the project score + """ + name: NotRequired[str | None] + """ + Name of the project score + """ + score_type: NotRequired[ProjectScoreType | None] + + +class ProjectScore(TypedDict): + categories: NotRequired[ProjectScoreCategories] + config: NotRequired[ProjectScoreConfig | None] + created: NotRequired[str | None] + """ + Date of project score creation + """ + description: NotRequired[str | None] + """ + Textual description of the project score + """ + id: str + """ + Unique identifier for the project score + """ + name: str + """ + Name of the project score + """ + position: NotRequired[str | None] + """ + An optional LexoRank-based string that sets the sort position for the score in the UI + """ + project_id: str + """ + Unique identifier for the project that the project score belongs under + """ + score_type: ProjectScoreType + user_id: str + + +class GetProjectScoreResponse(TypedDict): + objects: Sequence[ProjectScore] + """ + A list of project_score objects + """ diff --git a/py/src/braintrust/api/_generated/models/project_tags.py b/py/src/braintrust/api/_generated/models/project_tags.py new file mode 100644 index 00000000..40c04faa --- /dev/null +++ b/py/src/braintrust/api/_generated/models/project_tags.py @@ -0,0 +1,95 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 936b39c6f2052c11472f458accba4692d8fa35225bf95fd9efe4486d6ba3564c + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateProjectTag(TypedDict): + color: NotRequired[str | None] + """ + Color of the tag for the UI + """ + description: NotRequired[str | None] + """ + Textual description of the project tag + """ + name: str + """ + Name of the project tag + """ + project_id: str + """ + Unique identifier for the project that the project tag belongs under + """ + + +class PatchProjectTag(TypedDict): + color: NotRequired[str | None] + """ + Color of the tag for the UI + """ + description: NotRequired[str | None] + """ + Textual description of the project tag + """ + name: NotRequired[str | None] + """ + Name of the project tag + """ + + +class ProjectTag(TypedDict): + color: NotRequired[str | None] + """ + Color of the tag for the UI + """ + created: NotRequired[str | None] + """ + Date of project tag creation + """ + description: NotRequired[str | None] + """ + Textual description of the project tag + """ + id: str + """ + Unique identifier for the project tag + """ + name: str + """ + Name of the project tag + """ + position: NotRequired[str | None] + """ + An optional LexoRank-based string that sets the sort position for the tag in the UI + """ + project_id: str + """ + Unique identifier for the project that the project tag belongs under + """ + user_id: str + + +ProjectTagIdParam: TypeAlias = str +""" +ProjectTag id +""" + +ProjectTagName: TypeAlias = str +""" +Name of the project_tag to search for +""" + + +class GetProjectTagResponse(TypedDict): + objects: Sequence[ProjectTag] + """ + A list of project_tag objects + """ diff --git a/py/src/braintrust/api/_generated/models/projects.py b/py/src/braintrust/api/_generated/models/projects.py index 6468be82..97665477 100644 --- a/py/src/braintrust/api/_generated/models/projects.py +++ b/py/src/braintrust/api/_generated/models/projects.py @@ -4,10 +4,10 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: c31f920240f9bc3c3f3c225ca78b62abfaed5892c1fd3eedf11f231ef7e2d83e +# Content SHA-256: df42dc8e0c114ae0d22a847726c364f9b37f9f99778a6a9d34d427f288f93354 -from typing import Any, Literal, TypeAlias, TypedDict from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict from collections.abc import Mapping, Sequence from .common import FunctionTypeEnum diff --git a/py/src/braintrust/api/_generated/models/prompts.py b/py/src/braintrust/api/_generated/models/prompts.py index 41426c7b..f2f5644e 100644 --- a/py/src/braintrust/api/_generated/models/prompts.py +++ b/py/src/braintrust/api/_generated/models/prompts.py @@ -4,10 +4,10 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: 0ec7d11315d85b755a496e3e33ffe15ed9b3600a66bc2be20906b0ba311c76bc +# Content SHA-256: 3d883eb968ed182a65fc96192990b4435b1186ab15f1bf1c76023c90cc63d4a8 -from typing import Any, Literal, TypeAlias, TypedDict from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict from collections.abc import Mapping, Sequence from .common import FunctionTypeEnumNullish, PromptDataNullish diff --git a/py/src/braintrust/api/_generated/models/roles.py b/py/src/braintrust/api/_generated/models/roles.py new file mode 100644 index 00000000..16d7dc50 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/roles.py @@ -0,0 +1,143 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: a978d2137e33b725ba9fc1dc796c2fd8fd8b5be3f9d588371f5acab5e0f97b2f + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +from .common import AclObjectType, Permission + + +class MemberPermission(TypedDict): + permission: Permission + restrict_object_type: NotRequired[AclObjectType | None] + + +class Role(TypedDict): + created: NotRequired[str | None] + """ + Date of role creation + """ + deleted_at: NotRequired[str | None] + """ + Date of role deletion, or null if the role is still active + """ + description: NotRequired[str | None] + """ + Textual description of the role + """ + id: str + """ + Unique identifier for the role + """ + member_permissions: NotRequired[Sequence[MemberPermission] | None] + """ + (permission, restrict_object_type) tuples which belong to this role + """ + member_roles: NotRequired[Sequence[str] | None] + """ + Ids of the roles this role inherits from + + An inheriting role has all the permissions contained in its member roles, as well as all of their inherited permissions + """ + name: str + """ + Name of the role + """ + org_id: NotRequired[str | None] + """ + Unique id for the organization that the role belongs under + + A null org_id indicates a system role, which may be assigned to anybody and inherited by any other role, but cannot be edited. + + It is forbidden to change the org after creating a role + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the role + """ + + +RoleIdParam: TypeAlias = str +""" +Role id +""" + +RoleName: TypeAlias = str +""" +Name of the role to search for +""" + + +class CreateRole(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the role + """ + member_permissions: NotRequired[Sequence[MemberPermission] | None] + """ + (permission, restrict_object_type) tuples which belong to this role + """ + member_roles: NotRequired[Sequence[str] | None] + """ + Ids of the roles this role inherits from + + An inheriting role has all the permissions contained in its member roles, as well as all of their inherited permissions + """ + name: str + """ + Name of the role + """ + org_name: NotRequired[str | None] + """ + For nearly all users, this parameter should be unnecessary. But in the rare case that your API key belongs to multiple organizations, you may specify the name of the organization the role belongs in. + """ + + +class GetRoleResponse(TypedDict): + objects: Sequence[Role] + """ + A list of role objects + """ + + +class AddMemberPermission(TypedDict): + permission: Permission + restrict_object_type: NotRequired[AclObjectType | None] + + +class RemoveMemberPermission(TypedDict): + permission: Permission + restrict_object_type: NotRequired[AclObjectType | None] + + +class PatchRole(TypedDict): + add_member_permissions: NotRequired[Sequence[AddMemberPermission] | None] + """ + A list of permissions to add to the role + """ + add_member_roles: NotRequired[Sequence[str] | None] + """ + A list of role IDs to add to the role's inheriting-from set + """ + description: NotRequired[str | None] + """ + Textual description of the role + """ + name: NotRequired[str | None] + """ + Name of the role + """ + remove_member_permissions: NotRequired[Sequence[RemoveMemberPermission] | None] + """ + A list of permissions to remove from the role + """ + remove_member_roles: NotRequired[Sequence[str] | None] + """ + A list of role IDs to remove from the role's inheriting-from set + """ diff --git a/py/src/braintrust/api/_generated/models/service_tokens.py b/py/src/braintrust/api/_generated/models/service_tokens.py new file mode 100644 index 00000000..b2b9c6b0 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/service_tokens.py @@ -0,0 +1,112 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: d74398cc90ee2110cbc8b873528850dceb97f7ed78f9a57df08568c4337d1e35 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateServiceTokenOutput(TypedDict): + created: NotRequired[str | None] + """ + Date of service token creation + """ + expires_at: NotRequired[str | None] + """ + Date and time at which the service token expires. If null, the token never expires. + """ + id: str + """ + Unique identifier for the service token + """ + key: str + """ + The raw service token. It will only be exposed this one time + """ + name: str + """ + Name of the service token + """ + org_id: NotRequired[str | None] + """ + Unique identifier for the organization + """ + preview_name: str + service_account_email: NotRequired[str | None] + """ + The service account email (not routable) + """ + service_account_id: NotRequired[str | None] + """ + Unique identifier for the service token + """ + service_account_name: NotRequired[str | None] + """ + The service account name + """ + + +class DeleteServiceToken(TypedDict): + id: str + """ + Unique identifier for the service token. + """ + + +class ServiceToken(TypedDict): + created: NotRequired[str | None] + """ + Date of service token creation + """ + expires_at: NotRequired[str | None] + """ + Date and time at which the service token expires. If null, the token never expires. + """ + id: str + """ + Unique identifier for the service token + """ + name: str + """ + Name of the service token + """ + org_id: NotRequired[str | None] + """ + Unique identifier for the organization + """ + preview_name: str + service_account_email: NotRequired[str | None] + """ + The service account email (not routable) + """ + service_account_id: NotRequired[str | None] + """ + Unique identifier for the service token + """ + service_account_name: NotRequired[str | None] + """ + The service account name + """ + + +ServiceTokenIdParam: TypeAlias = str +""" +ServiceToken id +""" + +ServiceTokenName: TypeAlias = str +""" +Name of the service_token to search for +""" + + +class GetServiceTokenResponse(TypedDict): + objects: Sequence[ServiceToken] + """ + A list of service_token objects + """ diff --git a/py/src/braintrust/api/_generated/models/span_iframes.py b/py/src/braintrust/api/_generated/models/span_iframes.py new file mode 100644 index 00000000..0806ee43 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/span_iframes.py @@ -0,0 +1,110 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 2de5bfdfec105e4d78666d0370d71a7164e583f0403fde7e3f376889f8734952 + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class CreateSpanIFrame(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the span iframe + """ + name: str + """ + Name of the span iframe + """ + post_message: NotRequired[bool | None] + """ + Whether to post messages to the iframe containing the span's data. This is useful when you want to render more data than fits in the URL. + """ + project_id: str + """ + Unique identifier for the project that the span iframe belongs under + """ + url: str + """ + URL to embed the project viewer in an iframe + """ + + +class PatchSpanIFrame(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the span iframe + """ + name: NotRequired[str | None] + """ + Name of the span iframe + """ + post_message: NotRequired[bool | None] + """ + Whether to post messages to the iframe containing the span's data. This is useful when you want to render more data than fits in the URL. + """ + url: NotRequired[str | None] + """ + URL to embed the project viewer in an iframe + """ + + +class SpanIFrame(TypedDict): + created: NotRequired[str | None] + """ + Date of span iframe creation + """ + deleted_at: NotRequired[str | None] + """ + Date of span iframe deletion, or null if the span iframe is still active + """ + description: NotRequired[str | None] + """ + Textual description of the span iframe + """ + id: str + """ + Unique identifier for the span iframe + """ + name: str + """ + Name of the span iframe + """ + post_message: NotRequired[bool | None] + """ + Whether to post messages to the iframe containing the span's data. This is useful when you want to render more data than fits in the URL. + """ + project_id: str + """ + Unique identifier for the project that the span iframe belongs under + """ + url: str + """ + URL to embed the project viewer in an iframe + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the span iframe + """ + + +SpanIframeIdParam: TypeAlias = str +""" +SpanIframe id +""" + +SpanIframeName: TypeAlias = str +""" +Name of the span_iframe to search for +""" + + +class GetSpanIframeResponse(TypedDict): + objects: Sequence[SpanIFrame] + """ + A list of span_iframe objects + """ diff --git a/py/src/braintrust/api/_generated/models/users.py b/py/src/braintrust/api/_generated/models/users.py new file mode 100644 index 00000000..956705f6 --- /dev/null +++ b/py/src/braintrust/api/_generated/models/users.py @@ -0,0 +1,66 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 5623dc4e05cb6d3e728dcd6104d6c9dd880cf2ffea4d7f3725d34d997f242b5d + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + + +class User(TypedDict): + avatar_url: NotRequired[str | None] + """ + URL of the user's Avatar image + """ + created: NotRequired[str | None] + """ + Date of user creation + """ + email: NotRequired[str | None] + """ + The user's email + """ + family_name: NotRequired[str | None] + """ + Family name of the user + """ + given_name: NotRequired[str | None] + """ + Given name of the user + """ + id: str + """ + Unique identifier for the user + """ + + +UserEmail: TypeAlias = str | Sequence[str] +""" +Email of the user to search for. You may pass the param multiple times to filter for more than one email +""" + +UserFamilyName: TypeAlias = str | Sequence[str] +""" +Family name of the user to search for. You may pass the param multiple times to filter for more than one family name +""" + +UserGivenName: TypeAlias = str | Sequence[str] +""" +Given name of the user to search for. You may pass the param multiple times to filter for more than one given name +""" + +UserIdParam: TypeAlias = str +""" +User id +""" + + +class GetUserResponse(TypedDict): + objects: Sequence[User] + """ + A list of user objects + """ diff --git a/py/src/braintrust/api/_generated/models/views.py b/py/src/braintrust/api/_generated/models/views.py new file mode 100644 index 00000000..6b0ff54d --- /dev/null +++ b/py/src/braintrust/api/_generated/models/views.py @@ -0,0 +1,335 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: e8d2ba3c791d64bd9c9739303d52e284a43f64c4dec2ad20798b0279f7fa8bab + +from typing_extensions import NotRequired +from typing import Any, Literal, TypeAlias, TypedDict +from collections.abc import Mapping, Sequence + +from .common import AclObjectType + + +class DeleteView(TypedDict): + object_id: str + """ + The id of the object the view applies to + """ + object_type: AclObjectType + + +class ViewDataSearch(TypedDict): + filter: NotRequired[Sequence[Any] | None] + match: NotRequired[Sequence[Any] | None] + sort: NotRequired[Sequence[Any] | None] + tag: NotRequired[Sequence[Any] | None] + + +ViewIdParam: TypeAlias = str +""" +View id +""" + +ViewName: TypeAlias = str +""" +Name of the view to search for +""" + + +class Options(TypedDict): + chartVisibility: NotRequired[Mapping[str, bool] | None] + frameEnd: NotRequired[str | None] + frameStart: NotRequired[str | None] + groupBy: NotRequired[str | None] + projectId: NotRequired[str | None] + rangeValue: NotRequired[str | None] + spanType: NotRequired[Literal["range", "frame"] | None] + type: NotRequired[Literal["project", "experiment"] | None] + tzUTC: NotRequired[bool | None] + + +class ViewOptions1(TypedDict): + freezeColumns: NotRequired[bool | None] + options: Options + viewType: Literal["monitor"] + + +class ChartAnnotation(TypedDict): + id: str + text: str + + +class ExcludedMeasure(TypedDict): + type: Literal["none", "score", "metric", "metadata"] + value: str + + +class PointSizeMetric(TypedDict): + type: Literal["none", "score", "metric", "metadata"] + value: str + + +class SymbolGrouping(TypedDict): + type: Literal["none", "score", "metric", "metadata"] + value: str + + +TimeRangeFilter = TypedDict( + "TimeRangeFilter", + { + "from": str, + "to": str, + }, +) + + +class XAxis(TypedDict): + type: Literal["none", "score", "metric", "metadata"] + value: str + + +class YMetric(TypedDict): + type: Literal["none", "score", "metric", "metadata"] + value: str + + +class ViewOptions2(TypedDict): + chartAnnotations: NotRequired[Sequence[ChartAnnotation] | None] + chartHeight: NotRequired[float | None] + cluster: NotRequired[str | None] + columnOrder: NotRequired[Sequence[str] | None] + columnSizing: NotRequired[Mapping[str, float] | None] + columnVisibility: NotRequired[Mapping[str, bool] | None] + excludedMeasures: NotRequired[Sequence[ExcludedMeasure] | None] + freezeColumns: NotRequired[bool | None] + grouping: NotRequired[str | None] + layout: NotRequired[str | None] + pointSizeMetric: NotRequired[PointSizeMetric | None] + queryShape: NotRequired[Literal["traces", "spans", "topics"] | None] + rowHeight: NotRequired[str | None] + symbolGrouping: NotRequired[SymbolGrouping | None] + tallGroupRows: NotRequired[bool | None] + timeRangeFilter: NotRequired[str | TimeRangeFilter | None] + topicMapReportKey: NotRequired[str | None] + xAxis: NotRequired[XAxis | None] + xAxisAggregation: NotRequired[str | None] + """ + One of 'avg', 'sum', 'min', 'max', 'median', 'all' + """ + yMetric: NotRequired[YMetric | None] + + +ViewOptions: TypeAlias = ViewOptions1 | ViewOptions2 | None +""" +Options for the view in the app +""" + +ViewType: TypeAlias = ( + Literal[ + "projects", + "experiments", + "experiment", + "playgrounds", + "playground", + "datasets", + "dataset", + "prompts", + "parameters", + "tools", + "scorers", + "classifiers", + "logs", + "monitor", + "for_review_project_log", + "for_review_experiments", + "for_review_datasets", + ] + | None +) +""" +Type of object that the view corresponds to. +""" + + +class ViewData(TypedDict): + custom_charts: NotRequired[Any | None] + search: NotRequired[ViewDataSearch | None] + + +class CreateView(TypedDict): + deleted_at: NotRequired[str | None] + """ + Date of role deletion, or null if the role is still active + """ + description: NotRequired[str | None] + """ + Textual description of the view + """ + name: str + """ + Name of the view + """ + object_id: str + """ + The id of the object the view applies to + """ + object_type: AclObjectType + options: NotRequired[ViewOptions] + user_id: NotRequired[str | None] + """ + Identifies the user who created the view + """ + view_data: NotRequired[ViewData | None] + view_type: ( + Literal[ + "projects", + "experiments", + "experiment", + "playgrounds", + "playground", + "datasets", + "dataset", + "prompts", + "parameters", + "tools", + "scorers", + "classifiers", + "logs", + "monitor", + "for_review_project_log", + "for_review_experiments", + "for_review_datasets", + ] + | None + ) + """ + Type of object that the view corresponds to. + """ + + +class PatchView(TypedDict): + description: NotRequired[str | None] + """ + Textual description of the view + """ + name: NotRequired[str | None] + """ + Name of the view + """ + object_id: str + """ + The id of the object the view applies to + """ + object_type: AclObjectType + options: NotRequired[ViewOptions] + starred: NotRequired[bool] + """ + Whether the view is starred in its project + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the view + """ + view_data: NotRequired[ViewData | None] + view_type: NotRequired[ + Literal[ + "projects", + "experiments", + "experiment", + "playgrounds", + "playground", + "datasets", + "dataset", + "prompts", + "parameters", + "tools", + "scorers", + "classifiers", + "logs", + "monitor", + "for_review_project_log", + "for_review_experiments", + "for_review_datasets", + ] + | None + ] + """ + Type of object that the view corresponds to. + """ + + +class View(TypedDict): + created: NotRequired[str | None] + """ + Date of view creation + """ + deleted_at: NotRequired[str | None] + """ + Date of role deletion, or null if the role is still active + """ + description: NotRequired[str | None] + """ + Textual description of the view + """ + id: str + """ + Unique identifier for the view + """ + name: str + """ + Name of the view + """ + object_id: str + """ + The id of the object the view applies to + """ + object_type: AclObjectType + options: NotRequired[ViewOptions] + starred: NotRequired[bool] + """ + Whether the view is starred in its project + """ + updated_at: NotRequired[str | None] + """ + Date of last view update + """ + user_id: NotRequired[str | None] + """ + Identifies the user who created the view + """ + view_data: NotRequired[ViewData | None] + view_type: ( + Literal[ + "projects", + "experiments", + "experiment", + "playgrounds", + "playground", + "datasets", + "dataset", + "prompts", + "parameters", + "tools", + "scorers", + "classifiers", + "logs", + "monitor", + "for_review_project_log", + "for_review_experiments", + "for_review_datasets", + ] + | None + ) + """ + Type of object that the view corresponds to. + """ + + +class GetViewResponse(TypedDict): + objects: Sequence[View] + """ + A list of view objects + """ diff --git a/py/src/braintrust/api/_generated/org_automations.py b/py/src/braintrust/api/_generated/org_automations.py new file mode 100644 index 00000000..ecf72cb6 --- /dev/null +++ b/py/src/braintrust/api/_generated/org_automations.py @@ -0,0 +1,257 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 7c38b2fe1729ef5e1a3f54934cd8d021ffa9de488a5993adfc47bea072caf865 + +"""Generated OrgAutomations REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.org_automations import ( + CreateOrgAutomation, + GetOrgAutomationResponse, + OrgAutomation, + OrgAutomationIdParam, + OrgAutomationName, + PatchOrgAutomation, +) + + +POST_ORG_AUTOMATION = Operation( + operation_id="postOrgAutomation", + method="POST", + path="/v1/org_automation", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_ORG_AUTOMATION = Operation( + operation_id="putOrgAutomation", + method="PUT", + path="/v1/org_automation", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_ORG_AUTOMATION = Operation( + operation_id="getOrgAutomation", + method="GET", + path="/v1/org_automation", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="org_automation_name", + name="org_automation_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_ORG_AUTOMATION_ID = Operation( + operation_id="getOrgAutomationId", + method="GET", + path="/v1/org_automation/{org_automation_id}", + parameters=( + Parameter( + argument_name="org_automation_id", + name="org_automation_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_ORG_AUTOMATION_ID = Operation( + operation_id="patchOrgAutomationId", + method="PATCH", + path="/v1/org_automation/{org_automation_id}", + parameters=( + Parameter( + argument_name="org_automation_id", + name="org_automation_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_ORG_AUTOMATION_ID = Operation( + operation_id="deleteOrgAutomationId", + method="DELETE", + path="/v1/org_automation/{org_automation_id}", + parameters=( + Parameter( + argument_name="org_automation_id", + name="org_automation_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postOrgAutomation": POST_ORG_AUTOMATION, + "putOrgAutomation": PUT_ORG_AUTOMATION, + "getOrgAutomation": GET_ORG_AUTOMATION, + "getOrgAutomationId": GET_ORG_AUTOMATION_ID, + "patchOrgAutomationId": PATCH_ORG_AUTOMATION_ID, + "deleteOrgAutomationId": DELETE_ORG_AUTOMATION_ID, +} + + +class OrgAutomationsAPI(ResourceAPI): + """Generated OrgAutomations REST API.""" + + def post_org_automation( + self, + *, + body: "CreateOrgAutomation", + ) -> "OrgAutomation": + return cast( + "OrgAutomation", + self.execute( + POST_ORG_AUTOMATION, + body=body, + ), + ) + + def put_org_automation( + self, + *, + body: "CreateOrgAutomation", + ) -> "OrgAutomation": + return cast( + "OrgAutomation", + self.execute( + PUT_ORG_AUTOMATION, + body=body, + ), + ) + + def get_org_automation( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + org_automation_name: "OrgAutomationName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetOrgAutomationResponse": + return cast( + "GetOrgAutomationResponse", + self.execute( + GET_ORG_AUTOMATION, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "org_automation_name": org_automation_name, + "org_name": org_name, + }, + ), + ) + + def get_org_automation_id( + self, + org_automation_id: "OrgAutomationIdParam", + ) -> "OrgAutomation": + return cast( + "OrgAutomation", + self.execute( + GET_ORG_AUTOMATION_ID, + path_parameters={"org_automation_id": org_automation_id}, + ), + ) + + def patch_org_automation_id( + self, + org_automation_id: "OrgAutomationIdParam", + *, + body: "PatchOrgAutomation | None" = None, + ) -> "OrgAutomation": + return cast( + "OrgAutomation", + self.execute( + PATCH_ORG_AUTOMATION_ID, + path_parameters={"org_automation_id": org_automation_id}, + body=body, + ), + ) + + def delete_org_automation_id( + self, + org_automation_id: "OrgAutomationIdParam", + ) -> "OrgAutomation": + return cast( + "OrgAutomation", + self.execute( + DELETE_ORG_AUTOMATION_ID, + path_parameters={"org_automation_id": org_automation_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/organizations.py b/py/src/braintrust/api/_generated/organizations.py new file mode 100644 index 00000000..6793f25b --- /dev/null +++ b/py/src/braintrust/api/_generated/organizations.py @@ -0,0 +1,191 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 584a9ceaf40b36bdc383bac62d0942c96ccc17323e702ebb931b188234eef88e + +"""Generated Organizations REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.organizations import ( + GetOrganizationResponse, + Organization, + OrganizationIdParam, + PatchOrganization, + PatchOrganizationMembers, + PatchOrganizationMembersOutput, +) + + +GET_ORGANIZATION = Operation( + operation_id="getOrganization", + method="GET", + path="/v1/organization", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_ORGANIZATION_ID = Operation( + operation_id="getOrganizationId", + method="GET", + path="/v1/organization/{organization_id}", + parameters=( + Parameter( + argument_name="organization_id", + name="organization_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_ORGANIZATION_ID = Operation( + operation_id="patchOrganizationId", + method="PATCH", + path="/v1/organization/{organization_id}", + parameters=( + Parameter( + argument_name="organization_id", + name="organization_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PATCH_ORGANIZATION_MEMBERS = Operation( + operation_id="patchOrganizationMembers", + method="PATCH", + path="/v1/organization/members", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "getOrganization": GET_ORGANIZATION, + "getOrganizationId": GET_ORGANIZATION_ID, + "patchOrganizationId": PATCH_ORGANIZATION_ID, + "patchOrganizationMembers": PATCH_ORGANIZATION_MEMBERS, +} + + +class OrganizationsAPI(ResourceAPI): + """Generated Organizations REST API.""" + + def get_organization( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetOrganizationResponse": + return cast( + "GetOrganizationResponse", + self.execute( + GET_ORGANIZATION, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "org_name": org_name, + }, + ), + ) + + def get_organization_id( + self, + organization_id: "OrganizationIdParam", + ) -> "Organization": + return cast( + "Organization", + self.execute( + GET_ORGANIZATION_ID, + path_parameters={"organization_id": organization_id}, + ), + ) + + def patch_organization_id( + self, + organization_id: "OrganizationIdParam", + *, + body: "PatchOrganization | None" = None, + ) -> "Organization": + return cast( + "Organization", + self.execute( + PATCH_ORGANIZATION_ID, + path_parameters={"organization_id": organization_id}, + body=body, + ), + ) + + def patch_organization_members( + self, + *, + body: "PatchOrganizationMembers | None" = None, + ) -> "PatchOrganizationMembersOutput": + return cast( + "PatchOrganizationMembersOutput", + self.execute( + PATCH_ORGANIZATION_MEMBERS, + body=body, + ), + ) diff --git a/py/src/braintrust/api/_generated/project_automations.py b/py/src/braintrust/api/_generated/project_automations.py new file mode 100644 index 00000000..628c8259 --- /dev/null +++ b/py/src/braintrust/api/_generated/project_automations.py @@ -0,0 +1,257 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 2c3e34042b60d251ff81dfe467a1f2958d7baadf9e8888bac93c6c452b941b99 + +"""Generated ProjectAutomations REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.project_automations import ( + CreateProjectAutomation, + GetProjectAutomationResponse, + PatchProjectAutomation, + ProjectAutomation, + ProjectAutomationIdParam, + ProjectAutomationName, +) + + +POST_PROJECT_AUTOMATION = Operation( + operation_id="postProjectAutomation", + method="POST", + path="/v1/project_automation", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_PROJECT_AUTOMATION = Operation( + operation_id="putProjectAutomation", + method="PUT", + path="/v1/project_automation", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_PROJECT_AUTOMATION = Operation( + operation_id="getProjectAutomation", + method="GET", + path="/v1/project_automation", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="project_automation_name", + name="project_automation_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_PROJECT_AUTOMATION_ID = Operation( + operation_id="getProjectAutomationId", + method="GET", + path="/v1/project_automation/{project_automation_id}", + parameters=( + Parameter( + argument_name="project_automation_id", + name="project_automation_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_PROJECT_AUTOMATION_ID = Operation( + operation_id="patchProjectAutomationId", + method="PATCH", + path="/v1/project_automation/{project_automation_id}", + parameters=( + Parameter( + argument_name="project_automation_id", + name="project_automation_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_PROJECT_AUTOMATION_ID = Operation( + operation_id="deleteProjectAutomationId", + method="DELETE", + path="/v1/project_automation/{project_automation_id}", + parameters=( + Parameter( + argument_name="project_automation_id", + name="project_automation_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postProjectAutomation": POST_PROJECT_AUTOMATION, + "putProjectAutomation": PUT_PROJECT_AUTOMATION, + "getProjectAutomation": GET_PROJECT_AUTOMATION, + "getProjectAutomationId": GET_PROJECT_AUTOMATION_ID, + "patchProjectAutomationId": PATCH_PROJECT_AUTOMATION_ID, + "deleteProjectAutomationId": DELETE_PROJECT_AUTOMATION_ID, +} + + +class ProjectAutomationsAPI(ResourceAPI): + """Generated ProjectAutomations REST API.""" + + def post_project_automation( + self, + *, + body: "CreateProjectAutomation", + ) -> "ProjectAutomation": + return cast( + "ProjectAutomation", + self.execute( + POST_PROJECT_AUTOMATION, + body=body, + ), + ) + + def put_project_automation( + self, + *, + body: "CreateProjectAutomation", + ) -> "ProjectAutomation": + return cast( + "ProjectAutomation", + self.execute( + PUT_PROJECT_AUTOMATION, + body=body, + ), + ) + + def get_project_automation( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + project_automation_name: "ProjectAutomationName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetProjectAutomationResponse": + return cast( + "GetProjectAutomationResponse", + self.execute( + GET_PROJECT_AUTOMATION, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "project_automation_name": project_automation_name, + "org_name": org_name, + }, + ), + ) + + def get_project_automation_id( + self, + project_automation_id: "ProjectAutomationIdParam", + ) -> "ProjectAutomation": + return cast( + "ProjectAutomation", + self.execute( + GET_PROJECT_AUTOMATION_ID, + path_parameters={"project_automation_id": project_automation_id}, + ), + ) + + def patch_project_automation_id( + self, + project_automation_id: "ProjectAutomationIdParam", + *, + body: "PatchProjectAutomation | None" = None, + ) -> "ProjectAutomation": + return cast( + "ProjectAutomation", + self.execute( + PATCH_PROJECT_AUTOMATION_ID, + path_parameters={"project_automation_id": project_automation_id}, + body=body, + ), + ) + + def delete_project_automation_id( + self, + project_automation_id: "ProjectAutomationIdParam", + ) -> "ProjectAutomation": + return cast( + "ProjectAutomation", + self.execute( + DELETE_PROJECT_AUTOMATION_ID, + path_parameters={"project_automation_id": project_automation_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/project_groups.py b/py/src/braintrust/api/_generated/project_groups.py new file mode 100644 index 00000000..91b8c3b5 --- /dev/null +++ b/py/src/braintrust/api/_generated/project_groups.py @@ -0,0 +1,257 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 708923d503995b70db9549b1b15da470732ef8b7396944bb8b36db23b54869be + +"""Generated ProjectGroups REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.project_groups import ( + CreateProjectGroup, + GetProjectGroupResponse, + PatchProjectGroup, + ProjectGroup, + ProjectGroupIdParam, + ProjectGroupName, +) + + +POST_PROJECT_GROUP = Operation( + operation_id="postProjectGroup", + method="POST", + path="/v1/project_group", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_PROJECT_GROUP = Operation( + operation_id="putProjectGroup", + method="PUT", + path="/v1/project_group", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_PROJECT_GROUP = Operation( + operation_id="getProjectGroup", + method="GET", + path="/v1/project_group", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="project_group_name", + name="project_group_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_PROJECT_GROUP_ID = Operation( + operation_id="getProjectGroupId", + method="GET", + path="/v1/project_group/{project_group_id}", + parameters=( + Parameter( + argument_name="project_group_id", + name="project_group_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_PROJECT_GROUP_ID = Operation( + operation_id="patchProjectGroupId", + method="PATCH", + path="/v1/project_group/{project_group_id}", + parameters=( + Parameter( + argument_name="project_group_id", + name="project_group_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_PROJECT_GROUP_ID = Operation( + operation_id="deleteProjectGroupId", + method="DELETE", + path="/v1/project_group/{project_group_id}", + parameters=( + Parameter( + argument_name="project_group_id", + name="project_group_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postProjectGroup": POST_PROJECT_GROUP, + "putProjectGroup": PUT_PROJECT_GROUP, + "getProjectGroup": GET_PROJECT_GROUP, + "getProjectGroupId": GET_PROJECT_GROUP_ID, + "patchProjectGroupId": PATCH_PROJECT_GROUP_ID, + "deleteProjectGroupId": DELETE_PROJECT_GROUP_ID, +} + + +class ProjectGroupsAPI(ResourceAPI): + """Generated ProjectGroups REST API.""" + + def post_project_group( + self, + *, + body: "CreateProjectGroup", + ) -> "ProjectGroup": + return cast( + "ProjectGroup", + self.execute( + POST_PROJECT_GROUP, + body=body, + ), + ) + + def put_project_group( + self, + *, + body: "CreateProjectGroup", + ) -> "ProjectGroup": + return cast( + "ProjectGroup", + self.execute( + PUT_PROJECT_GROUP, + body=body, + ), + ) + + def get_project_group( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + project_group_name: "ProjectGroupName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetProjectGroupResponse": + return cast( + "GetProjectGroupResponse", + self.execute( + GET_PROJECT_GROUP, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "project_group_name": project_group_name, + "org_name": org_name, + }, + ), + ) + + def get_project_group_id( + self, + project_group_id: "ProjectGroupIdParam", + ) -> "ProjectGroup": + return cast( + "ProjectGroup", + self.execute( + GET_PROJECT_GROUP_ID, + path_parameters={"project_group_id": project_group_id}, + ), + ) + + def patch_project_group_id( + self, + project_group_id: "ProjectGroupIdParam", + *, + body: "PatchProjectGroup | None" = None, + ) -> "ProjectGroup": + return cast( + "ProjectGroup", + self.execute( + PATCH_PROJECT_GROUP_ID, + path_parameters={"project_group_id": project_group_id}, + body=body, + ), + ) + + def delete_project_group_id( + self, + project_group_id: "ProjectGroupIdParam", + ) -> "ProjectGroup": + return cast( + "ProjectGroup", + self.execute( + DELETE_PROJECT_GROUP_ID, + path_parameters={"project_group_id": project_group_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/project_scores.py b/py/src/braintrust/api/_generated/project_scores.py new file mode 100644 index 00000000..614e5688 --- /dev/null +++ b/py/src/braintrust/api/_generated/project_scores.py @@ -0,0 +1,284 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: b8c7db2fcb18903fe2e4e665e5c08e6341e492e7285f179c785c93f53c099a19 + +"""Generated ProjectScores REST operations and resource.""" + +from collections.abc import Sequence + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, ProjectIdQuery, ProjectName, StartingAfter +from .models.project_scores import ( + CreateProjectScore, + GetProjectScoreResponse, + PatchProjectScore, + ProjectScore, + ProjectScoreIdParam, + ProjectScoreName, + ProjectScoreType, +) + + +POST_PROJECT_SCORE = Operation( + operation_id="postProjectScore", + method="POST", + path="/v1/project_score", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_PROJECT_SCORE = Operation( + operation_id="putProjectScore", + method="PUT", + path="/v1/project_score", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_PROJECT_SCORE = Operation( + operation_id="getProjectScore", + method="GET", + path="/v1/project_score", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="project_score_name", + name="project_score_name", + location="query", + required=False, + ), + Parameter( + argument_name="project_name", + name="project_name", + location="query", + required=False, + ), + Parameter( + argument_name="project_id", + name="project_id", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + Parameter( + argument_name="score_type", + name="score_type", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_PROJECT_SCORE_ID = Operation( + operation_id="getProjectScoreId", + method="GET", + path="/v1/project_score/{project_score_id}", + parameters=( + Parameter( + argument_name="project_score_id", + name="project_score_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_PROJECT_SCORE_ID = Operation( + operation_id="patchProjectScoreId", + method="PATCH", + path="/v1/project_score/{project_score_id}", + parameters=( + Parameter( + argument_name="project_score_id", + name="project_score_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_PROJECT_SCORE_ID = Operation( + operation_id="deleteProjectScoreId", + method="DELETE", + path="/v1/project_score/{project_score_id}", + parameters=( + Parameter( + argument_name="project_score_id", + name="project_score_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postProjectScore": POST_PROJECT_SCORE, + "putProjectScore": PUT_PROJECT_SCORE, + "getProjectScore": GET_PROJECT_SCORE, + "getProjectScoreId": GET_PROJECT_SCORE_ID, + "patchProjectScoreId": PATCH_PROJECT_SCORE_ID, + "deleteProjectScoreId": DELETE_PROJECT_SCORE_ID, +} + + +class ProjectScoresAPI(ResourceAPI): + """Generated ProjectScores REST API.""" + + def post_project_score( + self, + *, + body: "CreateProjectScore", + ) -> "ProjectScore": + return cast( + "ProjectScore", + self.execute( + POST_PROJECT_SCORE, + body=body, + ), + ) + + def put_project_score( + self, + *, + body: "CreateProjectScore", + ) -> "ProjectScore": + return cast( + "ProjectScore", + self.execute( + PUT_PROJECT_SCORE, + body=body, + ), + ) + + def get_project_score( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + project_score_name: "ProjectScoreName | None" = None, + project_name: "ProjectName | None" = None, + project_id: "ProjectIdQuery | None" = None, + org_name: "OrgName | None" = None, + score_type: "ProjectScoreType | Sequence[ProjectScoreType] | None" = None, + ) -> "GetProjectScoreResponse": + return cast( + "GetProjectScoreResponse", + self.execute( + GET_PROJECT_SCORE, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "project_score_name": project_score_name, + "project_name": project_name, + "project_id": project_id, + "org_name": org_name, + "score_type": score_type, + }, + ), + ) + + def get_project_score_id( + self, + project_score_id: "ProjectScoreIdParam", + ) -> "ProjectScore": + return cast( + "ProjectScore", + self.execute( + GET_PROJECT_SCORE_ID, + path_parameters={"project_score_id": project_score_id}, + ), + ) + + def patch_project_score_id( + self, + project_score_id: "ProjectScoreIdParam", + *, + body: "PatchProjectScore | None" = None, + ) -> "ProjectScore": + return cast( + "ProjectScore", + self.execute( + PATCH_PROJECT_SCORE_ID, + path_parameters={"project_score_id": project_score_id}, + body=body, + ), + ) + + def delete_project_score_id( + self, + project_score_id: "ProjectScoreIdParam", + ) -> "ProjectScore": + return cast( + "ProjectScore", + self.execute( + DELETE_PROJECT_SCORE_ID, + path_parameters={"project_score_id": project_score_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/project_tags.py b/py/src/braintrust/api/_generated/project_tags.py new file mode 100644 index 00000000..53bd715a --- /dev/null +++ b/py/src/braintrust/api/_generated/project_tags.py @@ -0,0 +1,273 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: da2fc73d69463add8ff2c479671812972b30b4705c38c1edec9c9bf270077c19 + +"""Generated ProjectTags REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, ProjectIdQuery, ProjectName, StartingAfter +from .models.project_tags import ( + CreateProjectTag, + GetProjectTagResponse, + PatchProjectTag, + ProjectTag, + ProjectTagIdParam, + ProjectTagName, +) + + +POST_PROJECT_TAG = Operation( + operation_id="postProjectTag", + method="POST", + path="/v1/project_tag", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_PROJECT_TAG = Operation( + operation_id="putProjectTag", + method="PUT", + path="/v1/project_tag", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_PROJECT_TAG = Operation( + operation_id="getProjectTag", + method="GET", + path="/v1/project_tag", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="project_tag_name", + name="project_tag_name", + location="query", + required=False, + ), + Parameter( + argument_name="project_name", + name="project_name", + location="query", + required=False, + ), + Parameter( + argument_name="project_id", + name="project_id", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_PROJECT_TAG_ID = Operation( + operation_id="getProjectTagId", + method="GET", + path="/v1/project_tag/{project_tag_id}", + parameters=( + Parameter( + argument_name="project_tag_id", + name="project_tag_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_PROJECT_TAG_ID = Operation( + operation_id="patchProjectTagId", + method="PATCH", + path="/v1/project_tag/{project_tag_id}", + parameters=( + Parameter( + argument_name="project_tag_id", + name="project_tag_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_PROJECT_TAG_ID = Operation( + operation_id="deleteProjectTagId", + method="DELETE", + path="/v1/project_tag/{project_tag_id}", + parameters=( + Parameter( + argument_name="project_tag_id", + name="project_tag_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postProjectTag": POST_PROJECT_TAG, + "putProjectTag": PUT_PROJECT_TAG, + "getProjectTag": GET_PROJECT_TAG, + "getProjectTagId": GET_PROJECT_TAG_ID, + "patchProjectTagId": PATCH_PROJECT_TAG_ID, + "deleteProjectTagId": DELETE_PROJECT_TAG_ID, +} + + +class ProjectTagsAPI(ResourceAPI): + """Generated ProjectTags REST API.""" + + def post_project_tag( + self, + *, + body: "CreateProjectTag", + ) -> "ProjectTag": + return cast( + "ProjectTag", + self.execute( + POST_PROJECT_TAG, + body=body, + ), + ) + + def put_project_tag( + self, + *, + body: "CreateProjectTag", + ) -> "ProjectTag": + return cast( + "ProjectTag", + self.execute( + PUT_PROJECT_TAG, + body=body, + ), + ) + + def get_project_tag( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + project_tag_name: "ProjectTagName | None" = None, + project_name: "ProjectName | None" = None, + project_id: "ProjectIdQuery | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetProjectTagResponse": + return cast( + "GetProjectTagResponse", + self.execute( + GET_PROJECT_TAG, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "project_tag_name": project_tag_name, + "project_name": project_name, + "project_id": project_id, + "org_name": org_name, + }, + ), + ) + + def get_project_tag_id( + self, + project_tag_id: "ProjectTagIdParam", + ) -> "ProjectTag": + return cast( + "ProjectTag", + self.execute( + GET_PROJECT_TAG_ID, + path_parameters={"project_tag_id": project_tag_id}, + ), + ) + + def patch_project_tag_id( + self, + project_tag_id: "ProjectTagIdParam", + *, + body: "PatchProjectTag | None" = None, + ) -> "ProjectTag": + return cast( + "ProjectTag", + self.execute( + PATCH_PROJECT_TAG_ID, + path_parameters={"project_tag_id": project_tag_id}, + body=body, + ), + ) + + def delete_project_tag_id( + self, + project_tag_id: "ProjectTagIdParam", + ) -> "ProjectTag": + return cast( + "ProjectTag", + self.execute( + DELETE_PROJECT_TAG_ID, + path_parameters={"project_tag_id": project_tag_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/roles.py b/py/src/braintrust/api/_generated/roles.py new file mode 100644 index 00000000..58952238 --- /dev/null +++ b/py/src/braintrust/api/_generated/roles.py @@ -0,0 +1,250 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: c4bcfe22eb6413d0d926b0f3ddb501e0e1df941fcb0306f22df1a84a419875cd + +"""Generated Roles REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.roles import CreateRole, GetRoleResponse, PatchRole, Role, RoleIdParam, RoleName + + +POST_ROLE = Operation( + operation_id="postRole", + method="POST", + path="/v1/role", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_ROLE = Operation( + operation_id="putRole", + method="PUT", + path="/v1/role", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_ROLE = Operation( + operation_id="getRole", + method="GET", + path="/v1/role", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="role_name", + name="role_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_ROLE_ID = Operation( + operation_id="getRoleId", + method="GET", + path="/v1/role/{role_id}", + parameters=( + Parameter( + argument_name="role_id", + name="role_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_ROLE_ID = Operation( + operation_id="patchRoleId", + method="PATCH", + path="/v1/role/{role_id}", + parameters=( + Parameter( + argument_name="role_id", + name="role_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_ROLE_ID = Operation( + operation_id="deleteRoleId", + method="DELETE", + path="/v1/role/{role_id}", + parameters=( + Parameter( + argument_name="role_id", + name="role_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postRole": POST_ROLE, + "putRole": PUT_ROLE, + "getRole": GET_ROLE, + "getRoleId": GET_ROLE_ID, + "patchRoleId": PATCH_ROLE_ID, + "deleteRoleId": DELETE_ROLE_ID, +} + + +class RolesAPI(ResourceAPI): + """Generated Roles REST API.""" + + def post_role( + self, + *, + body: "CreateRole", + ) -> "Role": + return cast( + "Role", + self.execute( + POST_ROLE, + body=body, + ), + ) + + def put_role( + self, + *, + body: "CreateRole", + ) -> "Role": + return cast( + "Role", + self.execute( + PUT_ROLE, + body=body, + ), + ) + + def get_role( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + role_name: "RoleName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetRoleResponse": + return cast( + "GetRoleResponse", + self.execute( + GET_ROLE, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "role_name": role_name, + "org_name": org_name, + }, + ), + ) + + def get_role_id( + self, + role_id: "RoleIdParam", + ) -> "Role": + return cast( + "Role", + self.execute( + GET_ROLE_ID, + path_parameters={"role_id": role_id}, + ), + ) + + def patch_role_id( + self, + role_id: "RoleIdParam", + *, + body: "PatchRole | None" = None, + ) -> "Role": + return cast( + "Role", + self.execute( + PATCH_ROLE_ID, + path_parameters={"role_id": role_id}, + body=body, + ), + ) + + def delete_role_id( + self, + role_id: "RoleIdParam", + ) -> "Role": + return cast( + "Role", + self.execute( + DELETE_ROLE_ID, + path_parameters={"role_id": role_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/service_tokens.py b/py/src/braintrust/api/_generated/service_tokens.py new file mode 100644 index 00000000..ba2b19f0 --- /dev/null +++ b/py/src/braintrust/api/_generated/service_tokens.py @@ -0,0 +1,250 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 1d15b5a0fb7ad41da036560981ad6c360b21d1905a705299b05aad45fe621d6f + +"""Generated ServiceTokens REST operations and resource.""" + +from collections.abc import Mapping + +from typing import Any, cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.service_tokens import ( + CreateServiceTokenOutput, + DeleteServiceToken, + GetServiceTokenResponse, + ServiceToken, + ServiceTokenIdParam, + ServiceTokenName, +) + + +POST_SERVICE_TOKEN = Operation( + operation_id="postServiceToken", + method="POST", + path="/v1/service_token", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_SERVICE_TOKEN = Operation( + operation_id="putServiceToken", + method="PUT", + path="/v1/service_token", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_SERVICE_TOKEN = Operation( + operation_id="deleteServiceToken", + method="DELETE", + path="/v1/service_token", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_SERVICE_TOKEN = Operation( + operation_id="getServiceToken", + method="GET", + path="/v1/service_token", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="service_token_name", + name="service_token_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_SERVICE_TOKEN_ID = Operation( + operation_id="getServiceTokenId", + method="GET", + path="/v1/service_token/{service_token_id}", + parameters=( + Parameter( + argument_name="service_token_id", + name="service_token_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +DELETE_SERVICE_TOKEN_ID = Operation( + operation_id="deleteServiceTokenId", + method="DELETE", + path="/v1/service_token/{service_token_id}", + parameters=( + Parameter( + argument_name="service_token_id", + name="service_token_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postServiceToken": POST_SERVICE_TOKEN, + "putServiceToken": PUT_SERVICE_TOKEN, + "deleteServiceToken": DELETE_SERVICE_TOKEN, + "getServiceToken": GET_SERVICE_TOKEN, + "getServiceTokenId": GET_SERVICE_TOKEN_ID, + "deleteServiceTokenId": DELETE_SERVICE_TOKEN_ID, +} + + +class ServiceTokensAPI(ResourceAPI): + """Generated ServiceTokens REST API.""" + + def post_service_token( + self, + *, + body: "Mapping[str, Any]", + ) -> "CreateServiceTokenOutput": + return cast( + "CreateServiceTokenOutput", + self.execute( + POST_SERVICE_TOKEN, + body=body, + ), + ) + + def put_service_token( + self, + *, + body: "Mapping[str, Any]", + ) -> "CreateServiceTokenOutput": + return cast( + "CreateServiceTokenOutput", + self.execute( + PUT_SERVICE_TOKEN, + body=body, + ), + ) + + def delete_service_token( + self, + *, + body: "DeleteServiceToken", + ) -> "ServiceToken": + return cast( + "ServiceToken", + self.execute( + DELETE_SERVICE_TOKEN, + body=body, + ), + ) + + def get_service_token( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + service_token_name: "ServiceTokenName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetServiceTokenResponse": + return cast( + "GetServiceTokenResponse", + self.execute( + GET_SERVICE_TOKEN, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "service_token_name": service_token_name, + "org_name": org_name, + }, + ), + ) + + def get_service_token_id( + self, + service_token_id: "ServiceTokenIdParam", + ) -> "ServiceToken": + return cast( + "ServiceToken", + self.execute( + GET_SERVICE_TOKEN_ID, + path_parameters={"service_token_id": service_token_id}, + ), + ) + + def delete_service_token_id( + self, + service_token_id: "ServiceTokenIdParam", + ) -> "ServiceToken": + return cast( + "ServiceToken", + self.execute( + DELETE_SERVICE_TOKEN_ID, + path_parameters={"service_token_id": service_token_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/span_iframes.py b/py/src/braintrust/api/_generated/span_iframes.py new file mode 100644 index 00000000..82ad72a9 --- /dev/null +++ b/py/src/braintrust/api/_generated/span_iframes.py @@ -0,0 +1,257 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 8fa3b9e035bc8fe53b347f09a2339112ad2e1efbac19ed64b8b09c59bbc24322 + +"""Generated SpanIframes REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.span_iframes import ( + CreateSpanIFrame, + GetSpanIframeResponse, + PatchSpanIFrame, + SpanIFrame, + SpanIframeIdParam, + SpanIframeName, +) + + +POST_SPAN_IFRAME = Operation( + operation_id="postSpanIframe", + method="POST", + path="/v1/span_iframe", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_SPAN_IFRAME = Operation( + operation_id="putSpanIframe", + method="PUT", + path="/v1/span_iframe", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_SPAN_IFRAME = Operation( + operation_id="getSpanIframe", + method="GET", + path="/v1/span_iframe", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="span_iframe_name", + name="span_iframe_name", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_SPAN_IFRAME_ID = Operation( + operation_id="getSpanIframeId", + method="GET", + path="/v1/span_iframe/{span_iframe_id}", + parameters=( + Parameter( + argument_name="span_iframe_id", + name="span_iframe_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_SPAN_IFRAME_ID = Operation( + operation_id="patchSpanIframeId", + method="PATCH", + path="/v1/span_iframe/{span_iframe_id}", + parameters=( + Parameter( + argument_name="span_iframe_id", + name="span_iframe_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_SPAN_IFRAME_ID = Operation( + operation_id="deleteSpanIframeId", + method="DELETE", + path="/v1/span_iframe/{span_iframe_id}", + parameters=( + Parameter( + argument_name="span_iframe_id", + name="span_iframe_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postSpanIframe": POST_SPAN_IFRAME, + "putSpanIframe": PUT_SPAN_IFRAME, + "getSpanIframe": GET_SPAN_IFRAME, + "getSpanIframeId": GET_SPAN_IFRAME_ID, + "patchSpanIframeId": PATCH_SPAN_IFRAME_ID, + "deleteSpanIframeId": DELETE_SPAN_IFRAME_ID, +} + + +class SpanIframesAPI(ResourceAPI): + """Generated SpanIframes REST API.""" + + def post_span_iframe( + self, + *, + body: "CreateSpanIFrame", + ) -> "SpanIFrame": + return cast( + "SpanIFrame", + self.execute( + POST_SPAN_IFRAME, + body=body, + ), + ) + + def put_span_iframe( + self, + *, + body: "CreateSpanIFrame", + ) -> "SpanIFrame": + return cast( + "SpanIFrame", + self.execute( + PUT_SPAN_IFRAME, + body=body, + ), + ) + + def get_span_iframe( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + span_iframe_name: "SpanIframeName | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetSpanIframeResponse": + return cast( + "GetSpanIframeResponse", + self.execute( + GET_SPAN_IFRAME, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "span_iframe_name": span_iframe_name, + "org_name": org_name, + }, + ), + ) + + def get_span_iframe_id( + self, + span_iframe_id: "SpanIframeIdParam", + ) -> "SpanIFrame": + return cast( + "SpanIFrame", + self.execute( + GET_SPAN_IFRAME_ID, + path_parameters={"span_iframe_id": span_iframe_id}, + ), + ) + + def patch_span_iframe_id( + self, + span_iframe_id: "SpanIframeIdParam", + *, + body: "PatchSpanIFrame | None" = None, + ) -> "SpanIFrame": + return cast( + "SpanIFrame", + self.execute( + PATCH_SPAN_IFRAME_ID, + path_parameters={"span_iframe_id": span_iframe_id}, + body=body, + ), + ) + + def delete_span_iframe_id( + self, + span_iframe_id: "SpanIframeIdParam", + ) -> "SpanIFrame": + return cast( + "SpanIFrame", + self.execute( + DELETE_SPAN_IFRAME_ID, + path_parameters={"span_iframe_id": span_iframe_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/users.py b/py/src/braintrust/api/_generated/users.py new file mode 100644 index 00000000..d7233413 --- /dev/null +++ b/py/src/braintrust/api/_generated/users.py @@ -0,0 +1,147 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: 404e047f847440b5fa80272a48a817a77429a4bb9bd88cf71def43fdd0f11562 + +"""Generated Users REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AppLimitParam, EndingBefore, Ids, OrgName, StartingAfter +from .models.users import GetUserResponse, User, UserEmail, UserFamilyName, UserGivenName, UserIdParam + + +GET_USER = Operation( + operation_id="getUser", + method="GET", + path="/v1/user", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="given_name", + name="given_name", + location="query", + required=False, + ), + Parameter( + argument_name="family_name", + name="family_name", + location="query", + required=False, + ), + Parameter( + argument_name="email", + name="email", + location="query", + required=False, + ), + Parameter( + argument_name="org_name", + name="org_name", + location="query", + required=False, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_USER_ID = Operation( + operation_id="getUserId", + method="GET", + path="/v1/user/{user_id}", + parameters=( + Parameter( + argument_name="user_id", + name="user_id", + location="path", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +OPERATIONS = { + "getUser": GET_USER, + "getUserId": GET_USER_ID, +} + + +class UsersAPI(ResourceAPI): + """Generated Users REST API.""" + + def get_user( + self, + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + given_name: "UserGivenName | None" = None, + family_name: "UserFamilyName | None" = None, + email: "UserEmail | None" = None, + org_name: "OrgName | None" = None, + ) -> "GetUserResponse": + return cast( + "GetUserResponse", + self.execute( + GET_USER, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "given_name": given_name, + "family_name": family_name, + "email": email, + "org_name": org_name, + }, + ), + ) + + def get_user_id( + self, + user_id: "UserIdParam", + ) -> "User": + return cast( + "User", + self.execute( + GET_USER_ID, + path_parameters={"user_id": user_id}, + ), + ) diff --git a/py/src/braintrust/api/_generated/views.py b/py/src/braintrust/api/_generated/views.py new file mode 100644 index 00000000..92fabd05 --- /dev/null +++ b/py/src/braintrust/api/_generated/views.py @@ -0,0 +1,284 @@ +# Generated by scripts/generate-api-client.py. DO NOT EDIT. +# OpenAPI commit: 8d6e24ce78d8e33cbf9ba5f35d2fbc71749306d7 +# OpenAPI spec SHA-256: ebac33c1422e5673f875a254b3f7a13e4d8b5f5ab9b5095fd6b48e836613d507 +# datamodel-code-generator: 0.72.4 +# ruff: 0.15.21 +# Generator Python: 3.14 +# Content SHA-256: b3cdb3f1a5848a5fcbfe41ce2d20776a20c6b52c53d443ed9d8a69eb955757d7 + +"""Generated Views REST operations and resource.""" + +from typing import cast + +from .._service import Operation, Parameter, ResourceAPI +from ..policies import RetryMode +from .models.common import AclObjectId, AclObjectType, AppLimitParam, EndingBefore, Ids, StartingAfter +from .models.views import CreateView, DeleteView, GetViewResponse, PatchView, View, ViewIdParam, ViewName, ViewType + + +POST_VIEW = Operation( + operation_id="postView", + method="POST", + path="/v1/view", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +PUT_VIEW = Operation( + operation_id="putView", + method="PUT", + path="/v1/view", + parameters=(), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +GET_VIEW = Operation( + operation_id="getView", + method="GET", + path="/v1/view", + parameters=( + Parameter( + argument_name="limit", + name="limit", + location="query", + required=False, + ), + Parameter( + argument_name="starting_after", + name="starting_after", + location="query", + required=False, + ), + Parameter( + argument_name="ending_before", + name="ending_before", + location="query", + required=False, + ), + Parameter( + argument_name="ids", + name="ids", + location="query", + required=False, + ), + Parameter( + argument_name="view_name", + name="view_name", + location="query", + required=False, + ), + Parameter( + argument_name="view_type", + name="view_type", + location="query", + required=False, + ), + Parameter( + argument_name="object_type", + name="object_type", + location="query", + required=True, + ), + Parameter( + argument_name="object_id", + name="object_id", + location="query", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +GET_VIEW_ID = Operation( + operation_id="getViewId", + method="GET", + path="/v1/view/{view_id}", + parameters=( + Parameter( + argument_name="view_id", + name="view_id", + location="path", + required=True, + ), + Parameter( + argument_name="object_type", + name="object_type", + location="query", + required=True, + ), + Parameter( + argument_name="object_id", + name="object_id", + location="query", + required=True, + ), + ), + has_request_body=False, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.SAFE_READ, +) + + +PATCH_VIEW_ID = Operation( + operation_id="patchViewId", + method="PATCH", + path="/v1/view/{view_id}", + parameters=( + Parameter( + argument_name="view_id", + name="view_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +DELETE_VIEW_ID = Operation( + operation_id="deleteViewId", + method="DELETE", + path="/v1/view/{view_id}", + parameters=( + Parameter( + argument_name="view_id", + name="view_id", + location="path", + required=True, + ), + ), + has_request_body=True, + success_statuses=(200,), + json_success_statuses=(200,), + retry_mode=RetryMode.NONE, +) + + +OPERATIONS = { + "postView": POST_VIEW, + "putView": PUT_VIEW, + "getView": GET_VIEW, + "getViewId": GET_VIEW_ID, + "patchViewId": PATCH_VIEW_ID, + "deleteViewId": DELETE_VIEW_ID, +} + + +class ViewsAPI(ResourceAPI): + """Generated Views REST API.""" + + def post_view( + self, + *, + body: "CreateView", + ) -> "View": + return cast( + "View", + self.execute( + POST_VIEW, + body=body, + ), + ) + + def put_view( + self, + *, + body: "CreateView", + ) -> "View": + return cast( + "View", + self.execute( + PUT_VIEW, + body=body, + ), + ) + + def get_view( + self, + object_type: "AclObjectType", + object_id: "AclObjectId", + *, + limit: "AppLimitParam | None" = None, + starting_after: "StartingAfter | None" = None, + ending_before: "EndingBefore | None" = None, + ids: "Ids | None" = None, + view_name: "ViewName | None" = None, + view_type: "ViewType | None" = None, + ) -> "GetViewResponse": + return cast( + "GetViewResponse", + self.execute( + GET_VIEW, + query_parameters={ + "limit": limit, + "starting_after": starting_after, + "ending_before": ending_before, + "ids": ids, + "view_name": view_name, + "view_type": view_type, + "object_type": object_type, + "object_id": object_id, + }, + ), + ) + + def get_view_id( + self, + view_id: "ViewIdParam", + object_type: "AclObjectType", + object_id: "AclObjectId", + ) -> "View": + return cast( + "View", + self.execute( + GET_VIEW_ID, + path_parameters={"view_id": view_id}, + query_parameters={"object_type": object_type, "object_id": object_id}, + ), + ) + + def patch_view_id( + self, + view_id: "ViewIdParam", + *, + body: "PatchView", + ) -> "View": + return cast( + "View", + self.execute( + PATCH_VIEW_ID, + path_parameters={"view_id": view_id}, + body=body, + ), + ) + + def delete_view_id( + self, + view_id: "ViewIdParam", + *, + body: "DeleteView", + ) -> "View": + return cast( + "View", + self.execute( + DELETE_VIEW_ID, + path_parameters={"view_id": view_id}, + body=body, + ), + ) diff --git a/py/src/braintrust/api/client.py b/py/src/braintrust/api/client.py index 46e535a2..4efc11cf 100644 --- a/py/src/braintrust/api/client.py +++ b/py/src/braintrust/api/client.py @@ -155,18 +155,59 @@ def from_transport( return client def _initialize_services(self, api_key: str) -> None: + from ._generated.acls import AclsAPI + from ._generated.agents import AgentsAPI + from ._generated.ai_secrets import AiSecretsAPI + from ._generated.api_keys import ApiKeysAPI + from ._generated.dataset_snapshots import DatasetSnapshotsAPI from ._generated.datasets import DatasetsAPI + from ._generated.env_vars import EnvVarsAPI + from ._generated.environments import EnvironmentsAPI from ._generated.experiments import ExperimentsAPI from ._generated.functions import FunctionsAPI + from ._generated.groups import GroupsAPI + from ._generated.mcp_servers import McpServersAPI + from ._generated.org_automations import OrgAutomationsAPI + from ._generated.organizations import OrganizationsAPI + from ._generated.project_automations import ProjectAutomationsAPI + from ._generated.project_groups import ProjectGroupsAPI + from ._generated.project_scores import ProjectScoresAPI + from ._generated.project_tags import ProjectTagsAPI from ._generated.projects import ProjectsAPI from ._generated.prompts import PromptsAPI + from ._generated.roles import RolesAPI + from ._generated.service_tokens import ServiceTokensAPI + from ._generated.span_iframes import SpanIframesAPI + from ._generated.users import UsersAPI + from ._generated.views import ViewsAPI + service_args = (self.transport, self.router, api_key) self.api_key = api_key - self.datasets = DatasetsAPI(self.transport, self.router, api_key) - self.experiments = ExperimentsAPI(self.transport, self.router, api_key) - self.functions = FunctionsAPI(self.transport, self.router, api_key) - self.projects = ProjectsAPI(self.transport, self.router, api_key) - self.prompts = PromptsAPI(self.transport, self.router, api_key) + self.acls = AclsAPI(*service_args) + self.agents = AgentsAPI(*service_args) + self.ai_secrets = AiSecretsAPI(*service_args) + self.api_keys = ApiKeysAPI(*service_args) + self.dataset_snapshots = DatasetSnapshotsAPI(*service_args) + self.datasets = DatasetsAPI(*service_args) + self.env_vars = EnvVarsAPI(*service_args) + self.environments = EnvironmentsAPI(*service_args) + self.experiments = ExperimentsAPI(*service_args) + self.functions = FunctionsAPI(*service_args) + self.groups = GroupsAPI(*service_args) + self.mcp_servers = McpServersAPI(*service_args) + self.org_automations = OrgAutomationsAPI(*service_args) + self.organizations = OrganizationsAPI(*service_args) + self.project_automations = ProjectAutomationsAPI(*service_args) + self.project_groups = ProjectGroupsAPI(*service_args) + self.project_scores = ProjectScoresAPI(*service_args) + self.project_tags = ProjectTagsAPI(*service_args) + self.projects = ProjectsAPI(*service_args) + self.prompts = PromptsAPI(*service_args) + self.roles = RolesAPI(*service_args) + self.service_tokens = ServiceTokensAPI(*service_args) + self.span_iframes = SpanIframesAPI(*service_args) + self.users = UsersAPI(*service_args) + self.views = ViewsAPI(*service_args) def close(self) -> None: """Close the transport when it was created by this client.""" diff --git a/py/src/braintrust/api/test_generated_models.py b/py/src/braintrust/api/test_generated_models.py index 0f518614..96d6ff07 100644 --- a/py/src/braintrust/api/test_generated_models.py +++ b/py/src/braintrust/api/test_generated_models.py @@ -5,6 +5,35 @@ from typing import get_type_hints, is_typeddict +GENERATED_RESOURCE_NAMES = ( + "acls", + "agents", + "ai_secrets", + "api_keys", + "dataset_snapshots", + "datasets", + "env_vars", + "environments", + "experiments", + "functions", + "groups", + "mcp_servers", + "org_automations", + "organizations", + "project_automations", + "project_groups", + "project_scores", + "project_tags", + "projects", + "prompts", + "roles", + "service_tokens", + "span_iframes", + "users", + "views", +) + + def test_import_braintrust_is_lazy_about_generated_api_modules(): script = """ import json @@ -43,22 +72,26 @@ def test_generated_models_import_on_supported_python(): assert get_type_hints(prompt_bindings.PromptsAPI.get_prompt)["return"] is models.GetPromptResponse +def test_openapi_client_exposes_only_reviewed_generated_resources(): + from braintrust.api import BraintrustOpenApiClient + + with BraintrustOpenApiClient(api_key="test-key", api_url="https://api.example.com") as client: + assert all(hasattr(client, resource_name) for resource_name in GENERATED_RESOURCE_NAMES) + assert not any( + hasattr(client, resource_name) + for resource_name in ("cors", "cross_object", "evals", "logs", "other", "proxy") + ) + + def test_generated_package_content_is_installed(): generated = importlib.resources.files("braintrust.api._generated") assert generated.joinpath("__init__.py").is_file() assert generated.joinpath("models", "__init__.py").is_file() assert generated.joinpath("models", "common.py").is_file() - assert generated.joinpath("models", "datasets.py").is_file() - assert generated.joinpath("models", "experiments.py").is_file() - assert generated.joinpath("models", "functions.py").is_file() - assert generated.joinpath("models", "projects.py").is_file() - assert generated.joinpath("models", "prompts.py").is_file() - assert generated.joinpath("datasets.py").is_file() - assert generated.joinpath("experiments.py").is_file() - assert generated.joinpath("functions.py").is_file() - assert generated.joinpath("projects.py").is_file() - assert generated.joinpath("prompts.py").is_file() + for resource_name in GENERATED_RESOURCE_NAMES: + assert generated.joinpath("models", f"{resource_name}.py").is_file() + assert generated.joinpath(f"{resource_name}.py").is_file() def test_rest_and_logging_type_surfaces_have_reviewed_overlap(): @@ -67,4 +100,29 @@ def test_rest_and_logging_type_surfaces_have_reviewed_overlap(): overlap = set(generated_types.__all__) & set(types.__all__) - assert overlap == {"Dataset", "Experiment", "Function", "Project", "Prompt"} + assert overlap == { + "AISecret", + "Acl", + "Agent", + "ApiKey", + "Dataset", + "DatasetSnapshot", + "EnvVar", + "Experiment", + "Function", + "Group", + "MCPServer", + "OrgAutomation", + "Organization", + "Project", + "ProjectAutomation", + "ProjectGroup", + "ProjectScore", + "ProjectTag", + "Prompt", + "Role", + "ServiceToken", + "SpanIFrame", + "User", + "View", + } diff --git a/py/src/braintrust/api/types/__init__.py b/py/src/braintrust/api/types/__init__.py index 98b3cde4..cf8b2fba 100644 --- a/py/src/braintrust/api/types/__init__.py +++ b/py/src/braintrust/api/types/__init__.py @@ -1,12 +1,41 @@ """Public types for the synchronous Braintrust REST API.""" from .._generated.models import ( + Acl, + AclBatchUpdateRequest, + AclBatchUpdateResponse, + AclItem, + AclListOrgResponse, + Agent, + AISecret, + ApiKey, + CreateAgent, + CreateAISecret, CreateDataset, + CreateDatasetSnapshot, + CreateEnvironment, CreateExperiment, CreateFunction, + CreateGroup, + CreateMCPServer, + CreateOrgAutomation, CreateProject, + CreateProjectAutomation, + CreateProjectGroup, + CreateProjectScore, + CreateProjectTag, CreatePrompt, + CreateRole, + CreateServiceTokenOutput, + CreateSpanIFrame, + CreateView, Dataset, + DatasetSnapshot, + DeleteAISecret, + DeleteServiceToken, + DeleteView, + Environment, + EnvVar, Experiment, FeedbackDatasetEventRequest, FeedbackExperimentEventRequest, @@ -15,33 +44,112 @@ FetchEventsRequest, FetchExperimentEventsResponse, Function, + GetAclResponse, + GetAgentResponse, + GetAiSecretResponse, + GetApiKeyResponse, GetDatasetResponse, + GetDatasetSnapshotResponse, + GetEnvVarResponse, GetExperimentResponse, GetFunctionResponse, + GetGroupResponse, + GetMcpServerResponse, + GetOrganizationResponse, + GetOrgAutomationResponse, + GetProjectAutomationResponse, + GetProjectGroupResponse, GetProjectResponse, + GetProjectScoreResponse, + GetProjectTagResponse, GetPromptResponse, + GetRoleResponse, + GetServiceTokenResponse, + GetSpanIframeResponse, + GetUserResponse, + GetViewResponse, + Group, InsertDatasetEventRequest, InsertEventsResponse, InsertExperimentEventRequest, + ListEnvironmentsResponse, + MCPServer, + Organization, + OrgAutomation, + PatchAgent, + PatchAISecret, PatchDataset, + PatchDatasetSnapshot, + PatchEnvironment, PatchExperiment, PatchFunction, + PatchGroup, + PatchMCPServer, + PatchOrganization, + PatchOrganizationMembers, + PatchOrganizationMembersOutput, + PatchOrgAutomation, PatchProject, + PatchProjectAutomation, + PatchProjectGroup, + PatchProjectScore, + PatchProjectTag, PatchPrompt, + PatchRole, + PatchSpanIFrame, + PatchView, Project, + ProjectAutomation, + ProjectGroup, + ProjectScore, + ProjectTag, Prompt, + Role, + ServiceToken, + SpanIFrame, SummarizeDatasetResponse, SummarizeExperimentResponse, + User, + View, ) __all__ = [ + "AISecret", + "Acl", + "AclBatchUpdateRequest", + "AclBatchUpdateResponse", + "AclItem", + "AclListOrgResponse", + "Agent", + "ApiKey", + "CreateAISecret", + "CreateAgent", "CreateDataset", + "CreateDatasetSnapshot", + "CreateEnvironment", "CreateExperiment", "CreateFunction", + "CreateGroup", + "CreateMCPServer", + "CreateOrgAutomation", "CreateProject", + "CreateProjectAutomation", + "CreateProjectGroup", + "CreateProjectScore", + "CreateProjectTag", "CreatePrompt", + "CreateRole", + "CreateServiceTokenOutput", + "CreateSpanIFrame", + "CreateView", "Dataset", + "DatasetSnapshot", + "DeleteAISecret", + "DeleteServiceToken", + "DeleteView", + "EnvVar", + "Environment", "Experiment", "FeedbackDatasetEventRequest", "FeedbackExperimentEventRequest", @@ -50,21 +158,71 @@ "FetchEventsRequest", "FetchExperimentEventsResponse", "Function", + "GetAclResponse", + "GetAgentResponse", + "GetAiSecretResponse", + "GetApiKeyResponse", "GetDatasetResponse", + "GetDatasetSnapshotResponse", + "GetEnvVarResponse", "GetExperimentResponse", "GetFunctionResponse", + "GetGroupResponse", + "GetMcpServerResponse", + "GetOrgAutomationResponse", + "GetOrganizationResponse", + "GetProjectAutomationResponse", + "GetProjectGroupResponse", "GetProjectResponse", + "GetProjectScoreResponse", + "GetProjectTagResponse", "GetPromptResponse", + "GetRoleResponse", + "GetServiceTokenResponse", + "GetSpanIframeResponse", + "GetUserResponse", + "GetViewResponse", + "Group", "InsertDatasetEventRequest", "InsertEventsResponse", "InsertExperimentEventRequest", + "ListEnvironmentsResponse", + "MCPServer", + "OrgAutomation", + "Organization", + "PatchAISecret", + "PatchAgent", "PatchDataset", + "PatchDatasetSnapshot", + "PatchEnvironment", "PatchExperiment", "PatchFunction", + "PatchGroup", + "PatchMCPServer", + "PatchOrgAutomation", + "PatchOrganization", + "PatchOrganizationMembers", + "PatchOrganizationMembersOutput", "PatchProject", + "PatchProjectAutomation", + "PatchProjectGroup", + "PatchProjectScore", + "PatchProjectTag", "PatchPrompt", + "PatchRole", + "PatchSpanIFrame", + "PatchView", "Project", + "ProjectAutomation", + "ProjectGroup", + "ProjectScore", + "ProjectTag", "Prompt", + "Role", + "ServiceToken", + "SpanIFrame", "SummarizeDatasetResponse", "SummarizeExperimentResponse", + "User", + "View", ] diff --git a/py/src/braintrust/type_tests/test_api_client.py b/py/src/braintrust/type_tests/test_api_client.py index 3342a732..79943540 100644 --- a/py/src/braintrust/type_tests/test_api_client.py +++ b/py/src/braintrust/type_tests/test_api_client.py @@ -40,6 +40,28 @@ org_id: str = discovery.organization.id org_name: str = discovery.organization.name api_url: str | None = client.router.api_url + + openapi_client.acls.get_acl_id("acl-id") + openapi_client.agents.get_agent() + openapi_client.ai_secrets.get_ai_secret() + openapi_client.api_keys.get_api_key() + openapi_client.dataset_snapshots.get_dataset_snapshot() + openapi_client.env_vars.get_env_var() + openapi_client.environments.list_environments() + openapi_client.groups.get_group() + openapi_client.mcp_servers.get_mcp_server() + openapi_client.org_automations.get_org_automation() + openapi_client.organizations.get_organization() + openapi_client.project_automations.get_project_automation() + openapi_client.project_groups.get_project_group() + openapi_client.project_scores.get_project_score(score_type=["slider", "categorical"]) + openapi_client.project_tags.get_project_tag() + openapi_client.roles.get_role() + openapi_client.service_tokens.get_service_token() + openapi_client.span_iframes.get_span_iframe() + openapi_client.users.get_user() + openapi_client.views.get_view_id("view-id", "project", "project-id") + create_project: CreateProject = {"name": "typed-project"} patch_project: PatchProject = {"description": "updated"} project: Project = openapi_client.projects.post_project(body=create_project) diff --git a/py/tests/api_codegen/conftest.py b/py/tests/api_codegen/conftest.py index cf8b48fc..d745ae53 100644 --- a/py/tests/api_codegen/conftest.py +++ b/py/tests/api_codegen/conftest.py @@ -14,6 +14,7 @@ def codegen_config(): config = copy.deepcopy(load_config(CONFIG_PATH)) config["endpoint_generator"]["generated_tags"] = ["Widgets"] + config["endpoint_generator"]["unsupported_tags"] = {} config["endpoint_generator"]["safe_reads"] = [] config["endpoint_generator"]["idempotent_writes"] = [] config["endpoint_generator"]["specialized_operations"] = [] diff --git a/py/tests/api_codegen/test_generation.py b/py/tests/api_codegen/test_generation.py index f6b2bd5f..18b275df 100644 --- a/py/tests/api_codegen/test_generation.py +++ b/py/tests/api_codegen/test_generation.py @@ -5,10 +5,12 @@ import pytest from openapi_codegen import ( + _NON_MODEL_ANNOTATION_NAMES, CONFIG_PATH, GENERATED_ROOT, SPEC_PATH, CodegenError, + _collect_generated_operations, _snake_case, atomic_replace_tree, compare_generated, @@ -37,6 +39,7 @@ def test_generation_is_byte_for_byte_deterministic(tmp_path, codegen_config, min def test_generation_selects_generated_tag_regardless_of_tag_order(tmp_path, codegen_config, minimal_spec): minimal_spec["paths"]["/widgets/{widget_id}"]["get"]["tags"] = ["Internal", "Widgets"] + codegen_config["endpoint_generator"]["unsupported_tags"] = {"Internal": "Not a public resource."} generated = _generate(tmp_path, "secondary-generated-tag", codegen_config, minimal_spec) @@ -91,6 +94,71 @@ def test_pinned_selected_spec_operations_match_generated_registries(): ) +def test_pinned_unsupported_tags_are_explicit_and_proxy_is_excluded(): + config = load_config(CONFIG_PATH) + endpoint_config = config["endpoint_generator"] + + assert set(endpoint_config["unsupported_tags"]) == { + "CORS", + "CrossObject", + "Evals", + "Logs", + "Other", + "Proxy", + } + assert "Proxy" not in endpoint_config["generated_tags"] + assert not (GENERATED_ROOT / "proxy.py").exists() + + +def test_generated_tags_are_wired_to_openapi_client(): + config = load_config(CONFIG_PATH) + expected_resources = {_snake_case(tag) for tag in config["endpoint_generator"]["generated_tags"]} + client_tree = ast.parse((GENERATED_ROOT.parent / "client.py").read_text()) + openapi_client = next( + node for node in client_tree.body if isinstance(node, ast.ClassDef) and node.name == "BraintrustOpenApiClient" + ) + initializer = next( + node + for node in openapi_client.body + if isinstance(node, ast.FunctionDef) and node.name == "_initialize_services" + ) + initialized_resources = { + target.attr + for node in initializer.body + if isinstance(node, ast.Assign) + for target in node.targets + if isinstance(target, ast.Attribute) + and isinstance(target.value, ast.Name) + and target.value.id == "self" + and target.attr != "api_key" + } + + assert initialized_resources == expected_resources + + +def test_public_rest_types_match_generated_request_and_response_models(): + config = load_config(CONFIG_PATH) + spec = read_and_verify_spec(config, SPEC_PATH) + operations, _ = _collect_generated_operations(spec, config) + expected = set() + for operation in operations: + for type_name in (operation.request_body_type, operation.response_type): + if type_name: + expected.update(re.findall(r"\b[A-Z][A-Za-z0-9_]*\b", type_name)) + expected -= _NON_MODEL_ANNOTATION_NAMES + + public_types_path = GENERATED_ROOT.parent / "types" / "__init__.py" + public_types_tree = ast.parse(public_types_path.read_text()) + public_all = next( + node.value + for node in public_types_tree.body + if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets) + ) + assert isinstance(public_all, ast.List) + assert {element.value for element in public_all.elts if isinstance(element, ast.Constant)} == expected + + def test_inline_models_do_not_take_component_names(tmp_path, codegen_config, minimal_spec): minimal_spec["components"]["schemas"].update( { diff --git a/py/tests/api_codegen/test_validation.py b/py/tests/api_codegen/test_validation.py index 428a559a..d6bd2991 100644 --- a/py/tests/api_codegen/test_validation.py +++ b/py/tests/api_codegen/test_validation.py @@ -37,6 +37,11 @@ def test_only_allowlisted_operations_are_validated(minimal_spec, codegen_config) } } + codegen_config["endpoint_generator"]["unsupported_tags"] = { + "CORS": "Browser preflight is not a resource API.", + "Proxy": "Proxy operations require specialized streaming support.", + } + report = validate_spec(spec, codegen_config) assert report.operation_count == 1 @@ -65,6 +70,7 @@ def test_duplicate_operation_ids_fail_when_only_one_operation_is_selected(minima } spec["paths"]["/proxy"] = {"get": duplicate} spec["components"]["schemas"]["Unselected"] = {"type": "string"} + codegen_config["endpoint_generator"]["unsupported_tags"] = {"Proxy": "Specialized proxy transport."} with pytest.raises(CodegenError, match="Duplicate operationId"): validate_spec(spec, codegen_config) @@ -129,6 +135,83 @@ def test_media_types_and_success_statuses_are_validated(minimal_spec, codegen_co validate_spec(spec, codegen_config) +@pytest.mark.parametrize( + "tags", + [None, 1, "Widgets", {"Widgets": True}, [], [""], ["Widgets", 1]], +) +def test_operation_tags_must_be_a_non_empty_string_list(minimal_spec, codegen_config, tags): + minimal_spec["paths"]["/widgets/{widget_id}"]["get"]["tags"] = tags + + with pytest.raises(CodegenError, match="Operation 'getWidget' tags must be a list of non-empty strings"): + validate_spec(minimal_spec, codegen_config) + + +def test_absent_operation_tags_are_ignored(minimal_spec, codegen_config): + minimal_spec["paths"]["/untagged"] = { + "get": { + "operationId": "getUntagged", + "responses": {"200": {"description": "Unselected response is not validated"}}, + } + } + + assert validate_spec(minimal_spec, codegen_config).operation_count == 1 + + +def test_every_openapi_tag_must_be_generated_or_documented(minimal_spec, codegen_config): + minimal_spec["paths"]["/internal"] = { + "get": { + "operationId": "getInternal", + "tags": ["Internal"], + "responses": { + "200": { + "description": "OK", + "content": {"application/json": {"schema": {"type": "string"}}}, + } + }, + } + } + + with pytest.raises(CodegenError, match="unreviewed OpenAPI tags.*Internal"): + validate_spec(minimal_spec, codegen_config) + + codegen_config["endpoint_generator"]["unsupported_tags"] = {"Internal": "Not a public resource."} + assert validate_spec(minimal_spec, codegen_config).operation_count == 1 + + codegen_config["endpoint_generator"]["generated_tags"].append("Internal") + with pytest.raises(CodegenError, match="both generated and unsupported.*Internal"): + validate_spec(minimal_spec, codegen_config) + + +def test_unsupported_tags_require_reasons_and_must_exist(minimal_spec, codegen_config): + codegen_config["endpoint_generator"]["unsupported_tags"] = {"Internal": ""} + with pytest.raises(CodegenError, match="unsupported_tags must map tag names to non-empty reasons"): + validate_config(codegen_config, check_installed_tools=False) + + codegen_config["endpoint_generator"]["unsupported_tags"] = {"Internal": "Not a public resource."} + with pytest.raises(CodegenError, match="unsupported_tags contains unknown tags.*Internal"): + validate_spec(minimal_spec, codegen_config) + + +def test_parameter_all_of_metadata_wrapper_is_supported(minimal_spec, codegen_config): + minimal_spec["paths"]["/widgets/{widget_id}"]["get"]["parameters"].append( + { + "name": "kinds", + "in": "query", + "schema": { + "type": "array", + "items": { + "allOf": [ + {"type": "string", "enum": ["first", "second"]}, + {"title": "widget_kind"}, + ] + }, + }, + } + ) + + assert validate_spec(minimal_spec, codegen_config).operation_count == 1 + + def test_specialized_operations_must_belong_to_generated_tags(minimal_spec, codegen_config): codegen_config["endpoint_generator"]["specialized_operations"] = ["missingOperation"] @@ -259,6 +342,7 @@ def test_malformed_specs_and_configs_raise_actionable_errors(minimal_spec, codeg # A spec without any components is empty, not malformed -- it must not blow up on a missing key. empty_config = copy.deepcopy(codegen_config) empty_config["endpoint_generator"]["generated_tags"] = [] + empty_config["endpoint_generator"]["unsupported_tags"] = {} assert validate_spec({"openapi": "3.0.3", "paths": {}}, empty_config).schema_count == 0 with pytest.raises(CodegenError, match="components.schemas must be an object"): validate_spec({"openapi": "3.0.3", "paths": {}, "components": {"schemas": []}}, empty_config) @@ -272,6 +356,7 @@ def test_malformed_specs_and_configs_raise_actionable_errors(minimal_spec, codeg "safe_reads", "idempotent_writes", "specialized_operations", + "unsupported_tags", "supported_request_media_types", "supported_response_media_types", "supported_success_statuses", @@ -281,7 +366,11 @@ def test_malformed_specs_and_configs_raise_actionable_errors(minimal_spec, codeg message = ( f"endpoint_generator.{key} must be a unique list" if key in {"safe_reads", "idempotent_writes", "specialized_operations"} - else f"endpoint_generator.{key} must be a non-empty list" + else ( + "endpoint_generator.unsupported_tags must map tag names to non-empty reasons" + if key == "unsupported_tags" + else f"endpoint_generator.{key} must be a non-empty list" + ) ) with pytest.raises(CodegenError, match=message): validate_spec(minimal_spec, broken) From 08cc4fc0ab8fd11ebb32c58c30e2f23d41a997e3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 14 Sep 2026 13:40:54 -0400 Subject: [PATCH 2/2] lazy loading --- py/scripts/openapi_codegen.py | 33 +- .../api/_generated/models/__init__.py | 1426 +++++++++++------ py/src/braintrust/api/client.py | 208 ++- .../braintrust/api/test_generated_models.py | 50 + py/tests/api_codegen/test_generation.py | 23 +- 5 files changed, 1217 insertions(+), 523 deletions(-) diff --git a/py/scripts/openapi_codegen.py b/py/scripts/openapi_codegen.py index 9d3cb744..4a151673 100644 --- a/py/scripts/openapi_codegen.py +++ b/py/scripts/openapi_codegen.py @@ -713,12 +713,37 @@ def _model_package_source(model_modules: Mapping[str, str]) -> str: for name, module in model_modules.items(): by_module.setdefault(module, []).append(name) - lines = ['"""Generated private model types with stable package-level imports."""', ""] + lines = [ + '"""Generated private model types with lazy package-level imports."""', + "", + "from importlib import import_module", + "from typing import TYPE_CHECKING, Any", + "", + "", + "if TYPE_CHECKING:", + ] for module, names in sorted(by_module.items()): - lines.append(f"from .{module} import {', '.join(sorted(names))}") - lines.extend(["", "", "__all__ = ["]) + lines.append(f" from .{module} import {', '.join(sorted(names))}") + lines.extend(["", "", "_MODEL_MODULES = {"]) + lines.extend(f" {name!r}: {module!r}," for name, module in sorted(model_modules.items())) + lines.extend(["}", "", "", "__all__ = ["]) lines.extend(f" {name!r}," for name in sorted(model_modules)) - lines.extend(["]", ""]) + lines.extend( + [ + "]", + "", + "", + "def __getattr__(name: str) -> Any:", + " try:", + " module_name = _MODEL_MODULES[name]", + " except KeyError:", + ' raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None', + ' value = getattr(import_module(f"{__name__}.{module_name}"), name)', + " globals()[name] = value", + " return value", + "", + ] + ) return "\n".join(lines) diff --git a/py/src/braintrust/api/_generated/models/__init__.py b/py/src/braintrust/api/_generated/models/__init__.py index fabe1e15..40469e1e 100644 --- a/py/src/braintrust/api/_generated/models/__init__.py +++ b/py/src/braintrust/api/_generated/models/__init__.py @@ -4,480 +4,952 @@ # datamodel-code-generator: 0.72.4 # ruff: 0.15.21 # Generator Python: 3.14 -# Content SHA-256: c1dd7cb41842e16a5d40bf416da033db86114bb941267df342fe065e13872c83 +# Content SHA-256: 0f2c5474e1cd4459f43db9d0ffd7361245ea2fdec9d5e3cfe8bcfbdc496e4685 -"""Generated private model types with stable package-level imports.""" +"""Generated private model types with lazy package-level imports.""" -from .acls import ( - Acl, - AclBatchUpdateRequest, - AclBatchUpdateResponse, - AclIdParam, - AclItem, - AclListGroupId, - AclListOrgObjectId, - AclListOrgObjectType, - AclListOrgResponse, - AclListPermission, - AclListRestrictObjectType, - AclListRoleId, - AclListUserId, - GetAclResponse, -) -from .agents import Agent, AgentIdParam, AgentName, CreateAgent, GetAgentResponse, PatchAgent -from .ai_secrets import ( - AISecret, - AISecretType, - AiSecretIdParam, - AiSecretName, - CreateAISecret, - DeleteAISecret, - GetAiSecretResponse, - PatchAISecret, -) -from .api_keys import ApiKey, ApiKeyIdParam, ApiKeyName, GetApiKeyResponse -from .common import ( - AclObjectId, - AclObjectType, - AppLimitParam, - AutomationStatus, - CacheControl, - ChatCompletionContentPart, - ChatCompletionContentPartFileFile, - ChatCompletionContentPartFileWithTitle, - ChatCompletionContentPartImageWithTitle, - ChatCompletionContentPartText, - ChatCompletionContentPartTextWithTitle, - ChatCompletionMessageParam, - ChatCompletionMessageParam1, - ChatCompletionMessageParam2, - ChatCompletionMessageParam3, - ChatCompletionMessageParam4, - ChatCompletionMessageParam5, - ChatCompletionMessageParam6, - ChatCompletionMessageParam7, - ChatCompletionMessageReasoning, - ChatCompletionMessageToolCall, - ChatCompletionMessageToolCallFunction, - Classification, - Config4, - EndingBefore, - FeedbackResponseSchema, - FetchEventsRequest, - FetchLimit, - FetchLimitParam, - FetchPaginationCursor, - FieldArrayDeleteItem, - FunctionCall, - FunctionCall1, - FunctionTypeEnum, - FunctionTypeEnumNullish, - GroupScope, - Ids, - ImageUrl, - InsertEventsResponse, - MaxRootSpanId, - MaxXactId, - Mcp, - Mcp1, - Metadata, - ModelParams, - ModelParams1, - ModelParams2, - ModelParams3, - ModelParams4, - ModelParams5, - ModelParamsToolChoiceFunction, - ObjectReferenceNullish, - OrgName, - Origin2, - Permission, - PreprocessorId, - PreprocessorId1, - PreprocessorId2, - PreprocessorId3, - ProjectIdQuery, - ProjectName, - PromptBlockDataNullish, - PromptBlockDataNullish1, - PromptBlockDataNullish2, - PromptDataNullish, - PromptEnvironment, - PromptOptionsNullish, - PromptParserNullish, - PromptVersion, - ResponseFormatJsonSchema, - ResponseFormatNullish, - ResponseFormatNullish1, - ResponseFormatNullish2, - ResponseFormatNullish3, - RetentionObjectType, - SavedFunctionId, - SavedFunctionId1, - SavedFunctionId2, - Slug, - SpanScope, - StartingAfter, - ToolChoice, - ToolFunction, - ToolFunction1, - ToolFunction2, - ToolFunction3, - ToolFunction4, - ToolFunction5, - ToolFunction6, - ToolFunction7, - TraceScope, - Version, -) -from .dataset_snapshots import ( - CreateDatasetSnapshot, - DatasetSnapshot, - DatasetSnapshotIdParam, - DatasetSnapshotName, - GetDatasetSnapshotResponse, - PatchDatasetSnapshot, -) -from .datasets import ( - CreateDataset, - DataSummary, - Dataset, - DatasetEvent, - DatasetIdParam, - DatasetName, - FeedbackDatasetEventRequest, - FeedbackDatasetItem, - FetchDatasetEventsResponse, - GetDatasetResponse, - InsertDatasetEvent, - InsertDatasetEventRequest, - PatchDataset, - SummarizeData, - SummarizeDatasetResponse, -) -from .env_vars import EnvVar, EnvVarIdParam, EnvVarName, EnvVarObjectId, EnvVarObjectType, GetEnvVarResponse -from .environments import CreateEnvironment, Environment, ListEnvironmentsResponse, PatchEnvironment -from .experiments import ( - AppLimitWithDefaultParam, - ComparisonExperimentId, - Context, - CreateExperiment, - Experiment, - ExperimentEvent, - ExperimentIdParam, - ExperimentName, - FeedbackExperimentEventRequest, - FeedbackExperimentItem, - FetchExperimentEventsResponse, - GetExperimentResponse, - InsertExperimentEvent, - InsertExperimentEventRequest, - InternalMetadata, - MetricSummary, - Metrics, - PatchExperiment, - RepoInfo, - ScoreSummary, - SpanAttributes, - SpanType, - SummarizeExperimentResponse, - SummarizeScores, -) -from .functions import ( - BatchedFacetData, - CodeBundle, - CreateFunction, - Data, - Data1, - Data2, - Data3, - Facet, - FacetData, - FacetPreprocessorId, - FacetPreprocessorId1, - FacetPreprocessorId2, - FacetPreprocessorId3, - FieldSchema, - Function, - FunctionData, - FunctionData1, - FunctionData2, - FunctionData3, - FunctionData4, - FunctionData5, - FunctionDataNullish, - FunctionDataNullish1, - FunctionDataNullish2, - FunctionDataNullish3, - FunctionDataNullish4, - FunctionDataNullish5, - FunctionIdParam, - FunctionIdRef, - FunctionName, - FunctionSchema, - GetFunctionResponse, - GraphData, - GraphEdge, - GraphNode, - GraphNode1, - GraphNode2, - GraphNode3, - GraphNode4, - GraphNode5, - GraphNode6, - GraphNode7, - GraphNode8, - Location, - Location1, - Location2, - Origin, - PatchFunction, - Position, - Position1, - Position2, - Position3, - PromptBlockData, - PromptBlockData1, - PromptBlockData2, - RuntimeContext, - SandboxSpec, - SandboxSpec1, - Source, - SourceFacetFunction, - SourceFacetFunction1, - SourceFacetFunction2, - SourceFacetFunction3, - SourceFacetFunction4, - SourceFacetFunction5, - SourceFacetFunction6, - SourceFacetFunction7, - Target, - TopicMap, - TopicMapData, - TopicMapGenerationSettings, -) -from .groups import CreateGroup, GetGroupResponse, Group, GroupIdParam, GroupName, PatchGroup -from .mcp_servers import ( - CreateMCPServer, - GetMcpServerResponse, - MCPServer, - McpServerIdParam, - McpServerName, - PatchMCPServer, -) -from .org_automations import ( - Config, - CreateOrgAutomation, - GetOrgAutomationResponse, - OrgAutomation, - OrgAutomationIdParam, - OrgAutomationName, - PatchOrgAutomation, -) -from .organizations import ( - AddedUser, - GetOrganizationResponse, - ImageRenderingMode, - InviteUsers, - Organization, - OrganizationIdParam, - PatchOrganization, - PatchOrganizationMembers, - PatchOrganizationMembersOutput, - RemoveUsers, - ServiceAccount, -) -from .project_automations import ( - Action, - Action1, - Action10, - Action11, - Action2, - Action3, - Action4, - Action5, - Action6, - Action7, - Action8, - Action9, - Actions, - Actions1, - BackfillTimeRange, - Calculation, - Condition, - Config1, - Config10, - Config11, - Config12, - Config13, - Config14, - Config15, - Config16, - Config17, - Config2, - Config3, - Config5, - Config8, - Config9, - CreateProjectAutomation, - Credentials, - Credentials1, - Credentials2, - Credentials3, - Credentials4, - Credentials5, - ExportDefinition, - ExportDefinition1, - ExportDefinition2, - ExportDefinition3, - ExportDefinition4, - ExportDefinition5, - ExportDefinition6, - ExportDefinition7, - ExportDefinition8, - FacetFunction, - FacetFunction1, - FacetFunction2, - FacetFunction3, - FacetFunction4, - FacetFunction5, - FacetFunction6, - FacetFunction7, - Function1, - Function11, - Function12, - Function13, - Function14, - Function15, - Function16, - Function17, - GetProjectAutomationResponse, - Loop, - Output, - PatchProjectAutomation, - Policy, - ProjectAutomation, - ProjectAutomationIdParam, - ProjectAutomationName, - Schedule, - Schedule1, - Threshold, - TopicAutomationConfig, - TopicAutomationDataScope, - TopicAutomationDataScope1, - TopicAutomationDataScope2, - TopicAutomationDataScope3, - TopicAutomationFacetModel, - TopicDigestAutomationConfig, - TopicMapFunctionAutomation, - Window, - WindowedAutomationConfig, -) -from .project_groups import ( - CreateProjectGroup, - GetProjectGroupResponse, - PatchProjectGroup, - ProjectGroup, - ProjectGroupIdParam, - ProjectGroupName, -) -from .project_scores import ( - CreateProjectScore, - GetProjectScoreResponse, - OnlineScoreConfig, - PatchProjectScore, - ProjectScore, - ProjectScoreCategories, - ProjectScoreCategory, - ProjectScoreCondition, - ProjectScoreConfig, - ProjectScoreIdParam, - ProjectScoreName, - ProjectScoreType, - Scorer, - Scorer1, - Scorer2, - Scorer3, - Scorer4, - Scorer5, - Scorer6, - Scorer7, - Visibility, - When, -) -from .project_tags import ( - CreateProjectTag, - GetProjectTagResponse, - PatchProjectTag, - ProjectTag, - ProjectTagIdParam, - ProjectTagName, -) -from .projects import ( - CreateProject, - GetProjectResponse, - NullableSavedFunctionId, - NullableSavedFunctionId1, - NullableSavedFunctionId2, - PatchProject, - Project, - ProjectIdParam, - ProjectSettings, - RemoteEvalSource, - SpanFieldOrderItem, -) -from .prompts import CreatePrompt, GetPromptResponse, PatchPrompt, Prompt, PromptIdParam, PromptName -from .roles import ( - AddMemberPermission, - CreateRole, - GetRoleResponse, - MemberPermission, - PatchRole, - RemoveMemberPermission, - Role, - RoleIdParam, - RoleName, -) -from .service_tokens import ( - CreateServiceTokenOutput, - DeleteServiceToken, - GetServiceTokenResponse, - ServiceToken, - ServiceTokenIdParam, - ServiceTokenName, -) -from .span_iframes import ( - CreateSpanIFrame, - GetSpanIframeResponse, - PatchSpanIFrame, - SpanIFrame, - SpanIframeIdParam, - SpanIframeName, -) -from .users import GetUserResponse, User, UserEmail, UserFamilyName, UserGivenName, UserIdParam -from .views import ( - ChartAnnotation, - CreateView, - DeleteView, - ExcludedMeasure, - GetViewResponse, - Options, - PatchView, - PointSizeMetric, - SymbolGrouping, - TimeRangeFilter, - View, - ViewData, - ViewDataSearch, - ViewIdParam, - ViewName, - ViewOptions, - ViewOptions1, - ViewOptions2, - ViewType, - XAxis, - YMetric, -) +from importlib import import_module +from typing import TYPE_CHECKING, Any + + +if TYPE_CHECKING: + from .acls import ( + Acl, + AclBatchUpdateRequest, + AclBatchUpdateResponse, + AclIdParam, + AclItem, + AclListGroupId, + AclListOrgObjectId, + AclListOrgObjectType, + AclListOrgResponse, + AclListPermission, + AclListRestrictObjectType, + AclListRoleId, + AclListUserId, + GetAclResponse, + ) + from .agents import Agent, AgentIdParam, AgentName, CreateAgent, GetAgentResponse, PatchAgent + from .ai_secrets import ( + AISecret, + AISecretType, + AiSecretIdParam, + AiSecretName, + CreateAISecret, + DeleteAISecret, + GetAiSecretResponse, + PatchAISecret, + ) + from .api_keys import ApiKey, ApiKeyIdParam, ApiKeyName, GetApiKeyResponse + from .common import ( + AclObjectId, + AclObjectType, + AppLimitParam, + AutomationStatus, + CacheControl, + ChatCompletionContentPart, + ChatCompletionContentPartFileFile, + ChatCompletionContentPartFileWithTitle, + ChatCompletionContentPartImageWithTitle, + ChatCompletionContentPartText, + ChatCompletionContentPartTextWithTitle, + ChatCompletionMessageParam, + ChatCompletionMessageParam1, + ChatCompletionMessageParam2, + ChatCompletionMessageParam3, + ChatCompletionMessageParam4, + ChatCompletionMessageParam5, + ChatCompletionMessageParam6, + ChatCompletionMessageParam7, + ChatCompletionMessageReasoning, + ChatCompletionMessageToolCall, + ChatCompletionMessageToolCallFunction, + Classification, + Config4, + EndingBefore, + FeedbackResponseSchema, + FetchEventsRequest, + FetchLimit, + FetchLimitParam, + FetchPaginationCursor, + FieldArrayDeleteItem, + FunctionCall, + FunctionCall1, + FunctionTypeEnum, + FunctionTypeEnumNullish, + GroupScope, + Ids, + ImageUrl, + InsertEventsResponse, + MaxRootSpanId, + MaxXactId, + Mcp, + Mcp1, + Metadata, + ModelParams, + ModelParams1, + ModelParams2, + ModelParams3, + ModelParams4, + ModelParams5, + ModelParamsToolChoiceFunction, + ObjectReferenceNullish, + OrgName, + Origin2, + Permission, + PreprocessorId, + PreprocessorId1, + PreprocessorId2, + PreprocessorId3, + ProjectIdQuery, + ProjectName, + PromptBlockDataNullish, + PromptBlockDataNullish1, + PromptBlockDataNullish2, + PromptDataNullish, + PromptEnvironment, + PromptOptionsNullish, + PromptParserNullish, + PromptVersion, + ResponseFormatJsonSchema, + ResponseFormatNullish, + ResponseFormatNullish1, + ResponseFormatNullish2, + ResponseFormatNullish3, + RetentionObjectType, + SavedFunctionId, + SavedFunctionId1, + SavedFunctionId2, + Slug, + SpanScope, + StartingAfter, + ToolChoice, + ToolFunction, + ToolFunction1, + ToolFunction2, + ToolFunction3, + ToolFunction4, + ToolFunction5, + ToolFunction6, + ToolFunction7, + TraceScope, + Version, + ) + from .dataset_snapshots import ( + CreateDatasetSnapshot, + DatasetSnapshot, + DatasetSnapshotIdParam, + DatasetSnapshotName, + GetDatasetSnapshotResponse, + PatchDatasetSnapshot, + ) + from .datasets import ( + CreateDataset, + DataSummary, + Dataset, + DatasetEvent, + DatasetIdParam, + DatasetName, + FeedbackDatasetEventRequest, + FeedbackDatasetItem, + FetchDatasetEventsResponse, + GetDatasetResponse, + InsertDatasetEvent, + InsertDatasetEventRequest, + PatchDataset, + SummarizeData, + SummarizeDatasetResponse, + ) + from .env_vars import EnvVar, EnvVarIdParam, EnvVarName, EnvVarObjectId, EnvVarObjectType, GetEnvVarResponse + from .environments import CreateEnvironment, Environment, ListEnvironmentsResponse, PatchEnvironment + from .experiments import ( + AppLimitWithDefaultParam, + ComparisonExperimentId, + Context, + CreateExperiment, + Experiment, + ExperimentEvent, + ExperimentIdParam, + ExperimentName, + FeedbackExperimentEventRequest, + FeedbackExperimentItem, + FetchExperimentEventsResponse, + GetExperimentResponse, + InsertExperimentEvent, + InsertExperimentEventRequest, + InternalMetadata, + MetricSummary, + Metrics, + PatchExperiment, + RepoInfo, + ScoreSummary, + SpanAttributes, + SpanType, + SummarizeExperimentResponse, + SummarizeScores, + ) + from .functions import ( + BatchedFacetData, + CodeBundle, + CreateFunction, + Data, + Data1, + Data2, + Data3, + Facet, + FacetData, + FacetPreprocessorId, + FacetPreprocessorId1, + FacetPreprocessorId2, + FacetPreprocessorId3, + FieldSchema, + Function, + FunctionData, + FunctionData1, + FunctionData2, + FunctionData3, + FunctionData4, + FunctionData5, + FunctionDataNullish, + FunctionDataNullish1, + FunctionDataNullish2, + FunctionDataNullish3, + FunctionDataNullish4, + FunctionDataNullish5, + FunctionIdParam, + FunctionIdRef, + FunctionName, + FunctionSchema, + GetFunctionResponse, + GraphData, + GraphEdge, + GraphNode, + GraphNode1, + GraphNode2, + GraphNode3, + GraphNode4, + GraphNode5, + GraphNode6, + GraphNode7, + GraphNode8, + Location, + Location1, + Location2, + Origin, + PatchFunction, + Position, + Position1, + Position2, + Position3, + PromptBlockData, + PromptBlockData1, + PromptBlockData2, + RuntimeContext, + SandboxSpec, + SandboxSpec1, + Source, + SourceFacetFunction, + SourceFacetFunction1, + SourceFacetFunction2, + SourceFacetFunction3, + SourceFacetFunction4, + SourceFacetFunction5, + SourceFacetFunction6, + SourceFacetFunction7, + Target, + TopicMap, + TopicMapData, + TopicMapGenerationSettings, + ) + from .groups import CreateGroup, GetGroupResponse, Group, GroupIdParam, GroupName, PatchGroup + from .mcp_servers import ( + CreateMCPServer, + GetMcpServerResponse, + MCPServer, + McpServerIdParam, + McpServerName, + PatchMCPServer, + ) + from .org_automations import ( + Config, + CreateOrgAutomation, + GetOrgAutomationResponse, + OrgAutomation, + OrgAutomationIdParam, + OrgAutomationName, + PatchOrgAutomation, + ) + from .organizations import ( + AddedUser, + GetOrganizationResponse, + ImageRenderingMode, + InviteUsers, + Organization, + OrganizationIdParam, + PatchOrganization, + PatchOrganizationMembers, + PatchOrganizationMembersOutput, + RemoveUsers, + ServiceAccount, + ) + from .project_automations import ( + Action, + Action1, + Action10, + Action11, + Action2, + Action3, + Action4, + Action5, + Action6, + Action7, + Action8, + Action9, + Actions, + Actions1, + BackfillTimeRange, + Calculation, + Condition, + Config1, + Config10, + Config11, + Config12, + Config13, + Config14, + Config15, + Config16, + Config17, + Config2, + Config3, + Config5, + Config8, + Config9, + CreateProjectAutomation, + Credentials, + Credentials1, + Credentials2, + Credentials3, + Credentials4, + Credentials5, + ExportDefinition, + ExportDefinition1, + ExportDefinition2, + ExportDefinition3, + ExportDefinition4, + ExportDefinition5, + ExportDefinition6, + ExportDefinition7, + ExportDefinition8, + FacetFunction, + FacetFunction1, + FacetFunction2, + FacetFunction3, + FacetFunction4, + FacetFunction5, + FacetFunction6, + FacetFunction7, + Function1, + Function11, + Function12, + Function13, + Function14, + Function15, + Function16, + Function17, + GetProjectAutomationResponse, + Loop, + Output, + PatchProjectAutomation, + Policy, + ProjectAutomation, + ProjectAutomationIdParam, + ProjectAutomationName, + Schedule, + Schedule1, + Threshold, + TopicAutomationConfig, + TopicAutomationDataScope, + TopicAutomationDataScope1, + TopicAutomationDataScope2, + TopicAutomationDataScope3, + TopicAutomationFacetModel, + TopicDigestAutomationConfig, + TopicMapFunctionAutomation, + Window, + WindowedAutomationConfig, + ) + from .project_groups import ( + CreateProjectGroup, + GetProjectGroupResponse, + PatchProjectGroup, + ProjectGroup, + ProjectGroupIdParam, + ProjectGroupName, + ) + from .project_scores import ( + CreateProjectScore, + GetProjectScoreResponse, + OnlineScoreConfig, + PatchProjectScore, + ProjectScore, + ProjectScoreCategories, + ProjectScoreCategory, + ProjectScoreCondition, + ProjectScoreConfig, + ProjectScoreIdParam, + ProjectScoreName, + ProjectScoreType, + Scorer, + Scorer1, + Scorer2, + Scorer3, + Scorer4, + Scorer5, + Scorer6, + Scorer7, + Visibility, + When, + ) + from .project_tags import ( + CreateProjectTag, + GetProjectTagResponse, + PatchProjectTag, + ProjectTag, + ProjectTagIdParam, + ProjectTagName, + ) + from .projects import ( + CreateProject, + GetProjectResponse, + NullableSavedFunctionId, + NullableSavedFunctionId1, + NullableSavedFunctionId2, + PatchProject, + Project, + ProjectIdParam, + ProjectSettings, + RemoteEvalSource, + SpanFieldOrderItem, + ) + from .prompts import CreatePrompt, GetPromptResponse, PatchPrompt, Prompt, PromptIdParam, PromptName + from .roles import ( + AddMemberPermission, + CreateRole, + GetRoleResponse, + MemberPermission, + PatchRole, + RemoveMemberPermission, + Role, + RoleIdParam, + RoleName, + ) + from .service_tokens import ( + CreateServiceTokenOutput, + DeleteServiceToken, + GetServiceTokenResponse, + ServiceToken, + ServiceTokenIdParam, + ServiceTokenName, + ) + from .span_iframes import ( + CreateSpanIFrame, + GetSpanIframeResponse, + PatchSpanIFrame, + SpanIFrame, + SpanIframeIdParam, + SpanIframeName, + ) + from .users import GetUserResponse, User, UserEmail, UserFamilyName, UserGivenName, UserIdParam + from .views import ( + ChartAnnotation, + CreateView, + DeleteView, + ExcludedMeasure, + GetViewResponse, + Options, + PatchView, + PointSizeMetric, + SymbolGrouping, + TimeRangeFilter, + View, + ViewData, + ViewDataSearch, + ViewIdParam, + ViewName, + ViewOptions, + ViewOptions1, + ViewOptions2, + ViewType, + XAxis, + YMetric, + ) + + +_MODEL_MODULES = { + "AISecret": "ai_secrets", + "AISecretType": "ai_secrets", + "Acl": "acls", + "AclBatchUpdateRequest": "acls", + "AclBatchUpdateResponse": "acls", + "AclIdParam": "acls", + "AclItem": "acls", + "AclListGroupId": "acls", + "AclListOrgObjectId": "acls", + "AclListOrgObjectType": "acls", + "AclListOrgResponse": "acls", + "AclListPermission": "acls", + "AclListRestrictObjectType": "acls", + "AclListRoleId": "acls", + "AclListUserId": "acls", + "AclObjectId": "common", + "AclObjectType": "common", + "Action": "project_automations", + "Action1": "project_automations", + "Action10": "project_automations", + "Action11": "project_automations", + "Action2": "project_automations", + "Action3": "project_automations", + "Action4": "project_automations", + "Action5": "project_automations", + "Action6": "project_automations", + "Action7": "project_automations", + "Action8": "project_automations", + "Action9": "project_automations", + "Actions": "project_automations", + "Actions1": "project_automations", + "AddMemberPermission": "roles", + "AddedUser": "organizations", + "Agent": "agents", + "AgentIdParam": "agents", + "AgentName": "agents", + "AiSecretIdParam": "ai_secrets", + "AiSecretName": "ai_secrets", + "ApiKey": "api_keys", + "ApiKeyIdParam": "api_keys", + "ApiKeyName": "api_keys", + "AppLimitParam": "common", + "AppLimitWithDefaultParam": "experiments", + "AutomationStatus": "common", + "BackfillTimeRange": "project_automations", + "BatchedFacetData": "functions", + "CacheControl": "common", + "Calculation": "project_automations", + "ChartAnnotation": "views", + "ChatCompletionContentPart": "common", + "ChatCompletionContentPartFileFile": "common", + "ChatCompletionContentPartFileWithTitle": "common", + "ChatCompletionContentPartImageWithTitle": "common", + "ChatCompletionContentPartText": "common", + "ChatCompletionContentPartTextWithTitle": "common", + "ChatCompletionMessageParam": "common", + "ChatCompletionMessageParam1": "common", + "ChatCompletionMessageParam2": "common", + "ChatCompletionMessageParam3": "common", + "ChatCompletionMessageParam4": "common", + "ChatCompletionMessageParam5": "common", + "ChatCompletionMessageParam6": "common", + "ChatCompletionMessageParam7": "common", + "ChatCompletionMessageReasoning": "common", + "ChatCompletionMessageToolCall": "common", + "ChatCompletionMessageToolCallFunction": "common", + "Classification": "common", + "CodeBundle": "functions", + "ComparisonExperimentId": "experiments", + "Condition": "project_automations", + "Config": "org_automations", + "Config1": "project_automations", + "Config10": "project_automations", + "Config11": "project_automations", + "Config12": "project_automations", + "Config13": "project_automations", + "Config14": "project_automations", + "Config15": "project_automations", + "Config16": "project_automations", + "Config17": "project_automations", + "Config2": "project_automations", + "Config3": "project_automations", + "Config4": "common", + "Config5": "project_automations", + "Config8": "project_automations", + "Config9": "project_automations", + "Context": "experiments", + "CreateAISecret": "ai_secrets", + "CreateAgent": "agents", + "CreateDataset": "datasets", + "CreateDatasetSnapshot": "dataset_snapshots", + "CreateEnvironment": "environments", + "CreateExperiment": "experiments", + "CreateFunction": "functions", + "CreateGroup": "groups", + "CreateMCPServer": "mcp_servers", + "CreateOrgAutomation": "org_automations", + "CreateProject": "projects", + "CreateProjectAutomation": "project_automations", + "CreateProjectGroup": "project_groups", + "CreateProjectScore": "project_scores", + "CreateProjectTag": "project_tags", + "CreatePrompt": "prompts", + "CreateRole": "roles", + "CreateServiceTokenOutput": "service_tokens", + "CreateSpanIFrame": "span_iframes", + "CreateView": "views", + "Credentials": "project_automations", + "Credentials1": "project_automations", + "Credentials2": "project_automations", + "Credentials3": "project_automations", + "Credentials4": "project_automations", + "Credentials5": "project_automations", + "Data": "functions", + "Data1": "functions", + "Data2": "functions", + "Data3": "functions", + "DataSummary": "datasets", + "Dataset": "datasets", + "DatasetEvent": "datasets", + "DatasetIdParam": "datasets", + "DatasetName": "datasets", + "DatasetSnapshot": "dataset_snapshots", + "DatasetSnapshotIdParam": "dataset_snapshots", + "DatasetSnapshotName": "dataset_snapshots", + "DeleteAISecret": "ai_secrets", + "DeleteServiceToken": "service_tokens", + "DeleteView": "views", + "EndingBefore": "common", + "EnvVar": "env_vars", + "EnvVarIdParam": "env_vars", + "EnvVarName": "env_vars", + "EnvVarObjectId": "env_vars", + "EnvVarObjectType": "env_vars", + "Environment": "environments", + "ExcludedMeasure": "views", + "Experiment": "experiments", + "ExperimentEvent": "experiments", + "ExperimentIdParam": "experiments", + "ExperimentName": "experiments", + "ExportDefinition": "project_automations", + "ExportDefinition1": "project_automations", + "ExportDefinition2": "project_automations", + "ExportDefinition3": "project_automations", + "ExportDefinition4": "project_automations", + "ExportDefinition5": "project_automations", + "ExportDefinition6": "project_automations", + "ExportDefinition7": "project_automations", + "ExportDefinition8": "project_automations", + "Facet": "functions", + "FacetData": "functions", + "FacetFunction": "project_automations", + "FacetFunction1": "project_automations", + "FacetFunction2": "project_automations", + "FacetFunction3": "project_automations", + "FacetFunction4": "project_automations", + "FacetFunction5": "project_automations", + "FacetFunction6": "project_automations", + "FacetFunction7": "project_automations", + "FacetPreprocessorId": "functions", + "FacetPreprocessorId1": "functions", + "FacetPreprocessorId2": "functions", + "FacetPreprocessorId3": "functions", + "FeedbackDatasetEventRequest": "datasets", + "FeedbackDatasetItem": "datasets", + "FeedbackExperimentEventRequest": "experiments", + "FeedbackExperimentItem": "experiments", + "FeedbackResponseSchema": "common", + "FetchDatasetEventsResponse": "datasets", + "FetchEventsRequest": "common", + "FetchExperimentEventsResponse": "experiments", + "FetchLimit": "common", + "FetchLimitParam": "common", + "FetchPaginationCursor": "common", + "FieldArrayDeleteItem": "common", + "FieldSchema": "functions", + "Function": "functions", + "Function1": "project_automations", + "Function11": "project_automations", + "Function12": "project_automations", + "Function13": "project_automations", + "Function14": "project_automations", + "Function15": "project_automations", + "Function16": "project_automations", + "Function17": "project_automations", + "FunctionCall": "common", + "FunctionCall1": "common", + "FunctionData": "functions", + "FunctionData1": "functions", + "FunctionData2": "functions", + "FunctionData3": "functions", + "FunctionData4": "functions", + "FunctionData5": "functions", + "FunctionDataNullish": "functions", + "FunctionDataNullish1": "functions", + "FunctionDataNullish2": "functions", + "FunctionDataNullish3": "functions", + "FunctionDataNullish4": "functions", + "FunctionDataNullish5": "functions", + "FunctionIdParam": "functions", + "FunctionIdRef": "functions", + "FunctionName": "functions", + "FunctionSchema": "functions", + "FunctionTypeEnum": "common", + "FunctionTypeEnumNullish": "common", + "GetAclResponse": "acls", + "GetAgentResponse": "agents", + "GetAiSecretResponse": "ai_secrets", + "GetApiKeyResponse": "api_keys", + "GetDatasetResponse": "datasets", + "GetDatasetSnapshotResponse": "dataset_snapshots", + "GetEnvVarResponse": "env_vars", + "GetExperimentResponse": "experiments", + "GetFunctionResponse": "functions", + "GetGroupResponse": "groups", + "GetMcpServerResponse": "mcp_servers", + "GetOrgAutomationResponse": "org_automations", + "GetOrganizationResponse": "organizations", + "GetProjectAutomationResponse": "project_automations", + "GetProjectGroupResponse": "project_groups", + "GetProjectResponse": "projects", + "GetProjectScoreResponse": "project_scores", + "GetProjectTagResponse": "project_tags", + "GetPromptResponse": "prompts", + "GetRoleResponse": "roles", + "GetServiceTokenResponse": "service_tokens", + "GetSpanIframeResponse": "span_iframes", + "GetUserResponse": "users", + "GetViewResponse": "views", + "GraphData": "functions", + "GraphEdge": "functions", + "GraphNode": "functions", + "GraphNode1": "functions", + "GraphNode2": "functions", + "GraphNode3": "functions", + "GraphNode4": "functions", + "GraphNode5": "functions", + "GraphNode6": "functions", + "GraphNode7": "functions", + "GraphNode8": "functions", + "Group": "groups", + "GroupIdParam": "groups", + "GroupName": "groups", + "GroupScope": "common", + "Ids": "common", + "ImageRenderingMode": "organizations", + "ImageUrl": "common", + "InsertDatasetEvent": "datasets", + "InsertDatasetEventRequest": "datasets", + "InsertEventsResponse": "common", + "InsertExperimentEvent": "experiments", + "InsertExperimentEventRequest": "experiments", + "InternalMetadata": "experiments", + "InviteUsers": "organizations", + "ListEnvironmentsResponse": "environments", + "Location": "functions", + "Location1": "functions", + "Location2": "functions", + "Loop": "project_automations", + "MCPServer": "mcp_servers", + "MaxRootSpanId": "common", + "MaxXactId": "common", + "Mcp": "common", + "Mcp1": "common", + "McpServerIdParam": "mcp_servers", + "McpServerName": "mcp_servers", + "MemberPermission": "roles", + "Metadata": "common", + "MetricSummary": "experiments", + "Metrics": "experiments", + "ModelParams": "common", + "ModelParams1": "common", + "ModelParams2": "common", + "ModelParams3": "common", + "ModelParams4": "common", + "ModelParams5": "common", + "ModelParamsToolChoiceFunction": "common", + "NullableSavedFunctionId": "projects", + "NullableSavedFunctionId1": "projects", + "NullableSavedFunctionId2": "projects", + "ObjectReferenceNullish": "common", + "OnlineScoreConfig": "project_scores", + "Options": "views", + "OrgAutomation": "org_automations", + "OrgAutomationIdParam": "org_automations", + "OrgAutomationName": "org_automations", + "OrgName": "common", + "Organization": "organizations", + "OrganizationIdParam": "organizations", + "Origin": "functions", + "Origin2": "common", + "Output": "project_automations", + "PatchAISecret": "ai_secrets", + "PatchAgent": "agents", + "PatchDataset": "datasets", + "PatchDatasetSnapshot": "dataset_snapshots", + "PatchEnvironment": "environments", + "PatchExperiment": "experiments", + "PatchFunction": "functions", + "PatchGroup": "groups", + "PatchMCPServer": "mcp_servers", + "PatchOrgAutomation": "org_automations", + "PatchOrganization": "organizations", + "PatchOrganizationMembers": "organizations", + "PatchOrganizationMembersOutput": "organizations", + "PatchProject": "projects", + "PatchProjectAutomation": "project_automations", + "PatchProjectGroup": "project_groups", + "PatchProjectScore": "project_scores", + "PatchProjectTag": "project_tags", + "PatchPrompt": "prompts", + "PatchRole": "roles", + "PatchSpanIFrame": "span_iframes", + "PatchView": "views", + "Permission": "common", + "PointSizeMetric": "views", + "Policy": "project_automations", + "Position": "functions", + "Position1": "functions", + "Position2": "functions", + "Position3": "functions", + "PreprocessorId": "common", + "PreprocessorId1": "common", + "PreprocessorId2": "common", + "PreprocessorId3": "common", + "Project": "projects", + "ProjectAutomation": "project_automations", + "ProjectAutomationIdParam": "project_automations", + "ProjectAutomationName": "project_automations", + "ProjectGroup": "project_groups", + "ProjectGroupIdParam": "project_groups", + "ProjectGroupName": "project_groups", + "ProjectIdParam": "projects", + "ProjectIdQuery": "common", + "ProjectName": "common", + "ProjectScore": "project_scores", + "ProjectScoreCategories": "project_scores", + "ProjectScoreCategory": "project_scores", + "ProjectScoreCondition": "project_scores", + "ProjectScoreConfig": "project_scores", + "ProjectScoreIdParam": "project_scores", + "ProjectScoreName": "project_scores", + "ProjectScoreType": "project_scores", + "ProjectSettings": "projects", + "ProjectTag": "project_tags", + "ProjectTagIdParam": "project_tags", + "ProjectTagName": "project_tags", + "Prompt": "prompts", + "PromptBlockData": "functions", + "PromptBlockData1": "functions", + "PromptBlockData2": "functions", + "PromptBlockDataNullish": "common", + "PromptBlockDataNullish1": "common", + "PromptBlockDataNullish2": "common", + "PromptDataNullish": "common", + "PromptEnvironment": "common", + "PromptIdParam": "prompts", + "PromptName": "prompts", + "PromptOptionsNullish": "common", + "PromptParserNullish": "common", + "PromptVersion": "common", + "RemoteEvalSource": "projects", + "RemoveMemberPermission": "roles", + "RemoveUsers": "organizations", + "RepoInfo": "experiments", + "ResponseFormatJsonSchema": "common", + "ResponseFormatNullish": "common", + "ResponseFormatNullish1": "common", + "ResponseFormatNullish2": "common", + "ResponseFormatNullish3": "common", + "RetentionObjectType": "common", + "Role": "roles", + "RoleIdParam": "roles", + "RoleName": "roles", + "RuntimeContext": "functions", + "SandboxSpec": "functions", + "SandboxSpec1": "functions", + "SavedFunctionId": "common", + "SavedFunctionId1": "common", + "SavedFunctionId2": "common", + "Schedule": "project_automations", + "Schedule1": "project_automations", + "ScoreSummary": "experiments", + "Scorer": "project_scores", + "Scorer1": "project_scores", + "Scorer2": "project_scores", + "Scorer3": "project_scores", + "Scorer4": "project_scores", + "Scorer5": "project_scores", + "Scorer6": "project_scores", + "Scorer7": "project_scores", + "ServiceAccount": "organizations", + "ServiceToken": "service_tokens", + "ServiceTokenIdParam": "service_tokens", + "ServiceTokenName": "service_tokens", + "Slug": "common", + "Source": "functions", + "SourceFacetFunction": "functions", + "SourceFacetFunction1": "functions", + "SourceFacetFunction2": "functions", + "SourceFacetFunction3": "functions", + "SourceFacetFunction4": "functions", + "SourceFacetFunction5": "functions", + "SourceFacetFunction6": "functions", + "SourceFacetFunction7": "functions", + "SpanAttributes": "experiments", + "SpanFieldOrderItem": "projects", + "SpanIFrame": "span_iframes", + "SpanIframeIdParam": "span_iframes", + "SpanIframeName": "span_iframes", + "SpanScope": "common", + "SpanType": "experiments", + "StartingAfter": "common", + "SummarizeData": "datasets", + "SummarizeDatasetResponse": "datasets", + "SummarizeExperimentResponse": "experiments", + "SummarizeScores": "experiments", + "SymbolGrouping": "views", + "Target": "functions", + "Threshold": "project_automations", + "TimeRangeFilter": "views", + "ToolChoice": "common", + "ToolFunction": "common", + "ToolFunction1": "common", + "ToolFunction2": "common", + "ToolFunction3": "common", + "ToolFunction4": "common", + "ToolFunction5": "common", + "ToolFunction6": "common", + "ToolFunction7": "common", + "TopicAutomationConfig": "project_automations", + "TopicAutomationDataScope": "project_automations", + "TopicAutomationDataScope1": "project_automations", + "TopicAutomationDataScope2": "project_automations", + "TopicAutomationDataScope3": "project_automations", + "TopicAutomationFacetModel": "project_automations", + "TopicDigestAutomationConfig": "project_automations", + "TopicMap": "functions", + "TopicMapData": "functions", + "TopicMapFunctionAutomation": "project_automations", + "TopicMapGenerationSettings": "functions", + "TraceScope": "common", + "User": "users", + "UserEmail": "users", + "UserFamilyName": "users", + "UserGivenName": "users", + "UserIdParam": "users", + "Version": "common", + "View": "views", + "ViewData": "views", + "ViewDataSearch": "views", + "ViewIdParam": "views", + "ViewName": "views", + "ViewOptions": "views", + "ViewOptions1": "views", + "ViewOptions2": "views", + "ViewType": "views", + "Visibility": "project_scores", + "When": "project_scores", + "Window": "project_automations", + "WindowedAutomationConfig": "project_automations", + "XAxis": "views", + "YMetric": "views", +} __all__ = [ @@ -945,3 +1417,13 @@ "XAxis", "YMetric", ] + + +def __getattr__(name: str) -> Any: + try: + module_name = _MODEL_MODULES[name] + except KeyError: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None + value = getattr(import_module(f"{__name__}.{module_name}"), name) + globals()[name] = value + return value diff --git a/py/src/braintrust/api/client.py b/py/src/braintrust/api/client.py index 4efc11cf..54041f92 100644 --- a/py/src/braintrust/api/client.py +++ b/py/src/braintrust/api/client.py @@ -1,16 +1,49 @@ """Synchronous Braintrust API clients.""" -from typing import Any +from threading import Lock +from typing import TYPE_CHECKING, Any, TypeVar, cast import requests from requests.adapters import HTTPAdapter from ..env import BraintrustEnv, resolve_app_url from ._routing import EndpointRouter +from ._service import ResourceAPI from ._transport import HTTPConnection, Transport from .auth import AuthAPI +if TYPE_CHECKING: + from ._generated.acls import AclsAPI + from ._generated.agents import AgentsAPI + from ._generated.ai_secrets import AiSecretsAPI + from ._generated.api_keys import ApiKeysAPI + from ._generated.dataset_snapshots import DatasetSnapshotsAPI + from ._generated.datasets import DatasetsAPI + from ._generated.env_vars import EnvVarsAPI + from ._generated.environments import EnvironmentsAPI + from ._generated.experiments import ExperimentsAPI + from ._generated.functions import FunctionsAPI + from ._generated.groups import GroupsAPI + from ._generated.mcp_servers import McpServersAPI + from ._generated.org_automations import OrgAutomationsAPI + from ._generated.organizations import OrganizationsAPI + from ._generated.project_automations import ProjectAutomationsAPI + from ._generated.project_groups import ProjectGroupsAPI + from ._generated.project_scores import ProjectScoresAPI + from ._generated.project_tags import ProjectTagsAPI + from ._generated.projects import ProjectsAPI + from ._generated.prompts import PromptsAPI + from ._generated.roles import RolesAPI + from ._generated.service_tokens import ServiceTokensAPI + from ._generated.span_iframes import SpanIframesAPI + from ._generated.users import UsersAPI + from ._generated.views import ViewsAPI + + +_ServiceT = TypeVar("_ServiceT", bound=ResourceAPI) + + def _resolve_api_key(api_key: str | None) -> str: resolved_api_key = api_key or BraintrustEnv.API_KEY.get(None, use_dotenv=True) if not resolved_api_key: @@ -104,8 +137,9 @@ def __exit__(self, *_: Any) -> None: class BraintrustOpenApiClient: """Synchronous resource-oriented client for the Braintrust REST API. - Construction performs no network requests. Use :class:`BraintrustClient` - when authentication and generated resources should share one transport. + Construction performs no network requests. Generated resources are imported and cached on + first access. Use :class:`BraintrustClient` when authentication and generated resources should + share one transport. """ def __init__( @@ -135,7 +169,7 @@ def __init__( api_url=resolved_api_url, proxy_url=resolved_proxy_url, ) - self._initialize_services(resolved_api_key) + self._initialize_service_cache(resolved_api_key) @classmethod def from_transport( @@ -151,63 +185,171 @@ def from_transport( client._owns_transport = False client.transport = transport client.router = router - client._initialize_services(HTTPConnection.sanitize_token(api_key)) + client._initialize_service_cache(HTTPConnection.sanitize_token(api_key)) return client - def _initialize_services(self, api_key: str) -> None: + def _initialize_service_cache(self, api_key: str) -> None: + self.api_key = api_key + self._services: dict[str, ResourceAPI] = {} + self._services_lock = Lock() + + def _service(self, name: str, service_type: type[_ServiceT]) -> _ServiceT: + with self._services_lock: + service = self._services.get(name) + if service is None: + service = service_type(self.transport, self.router, self.api_key) + self._services[name] = service + return cast(_ServiceT, service) + + @property + def acls(self) -> "AclsAPI": from ._generated.acls import AclsAPI + + return self._service("acls", AclsAPI) + + @property + def agents(self) -> "AgentsAPI": from ._generated.agents import AgentsAPI + + return self._service("agents", AgentsAPI) + + @property + def ai_secrets(self) -> "AiSecretsAPI": from ._generated.ai_secrets import AiSecretsAPI + + return self._service("ai_secrets", AiSecretsAPI) + + @property + def api_keys(self) -> "ApiKeysAPI": from ._generated.api_keys import ApiKeysAPI + + return self._service("api_keys", ApiKeysAPI) + + @property + def dataset_snapshots(self) -> "DatasetSnapshotsAPI": from ._generated.dataset_snapshots import DatasetSnapshotsAPI + + return self._service("dataset_snapshots", DatasetSnapshotsAPI) + + @property + def datasets(self) -> "DatasetsAPI": from ._generated.datasets import DatasetsAPI + + return self._service("datasets", DatasetsAPI) + + @property + def env_vars(self) -> "EnvVarsAPI": from ._generated.env_vars import EnvVarsAPI + + return self._service("env_vars", EnvVarsAPI) + + @property + def environments(self) -> "EnvironmentsAPI": from ._generated.environments import EnvironmentsAPI + + return self._service("environments", EnvironmentsAPI) + + @property + def experiments(self) -> "ExperimentsAPI": from ._generated.experiments import ExperimentsAPI + + return self._service("experiments", ExperimentsAPI) + + @property + def functions(self) -> "FunctionsAPI": from ._generated.functions import FunctionsAPI + + return self._service("functions", FunctionsAPI) + + @property + def groups(self) -> "GroupsAPI": from ._generated.groups import GroupsAPI + + return self._service("groups", GroupsAPI) + + @property + def mcp_servers(self) -> "McpServersAPI": from ._generated.mcp_servers import McpServersAPI + + return self._service("mcp_servers", McpServersAPI) + + @property + def org_automations(self) -> "OrgAutomationsAPI": from ._generated.org_automations import OrgAutomationsAPI + + return self._service("org_automations", OrgAutomationsAPI) + + @property + def organizations(self) -> "OrganizationsAPI": from ._generated.organizations import OrganizationsAPI + + return self._service("organizations", OrganizationsAPI) + + @property + def project_automations(self) -> "ProjectAutomationsAPI": from ._generated.project_automations import ProjectAutomationsAPI + + return self._service("project_automations", ProjectAutomationsAPI) + + @property + def project_groups(self) -> "ProjectGroupsAPI": from ._generated.project_groups import ProjectGroupsAPI + + return self._service("project_groups", ProjectGroupsAPI) + + @property + def project_scores(self) -> "ProjectScoresAPI": from ._generated.project_scores import ProjectScoresAPI + + return self._service("project_scores", ProjectScoresAPI) + + @property + def project_tags(self) -> "ProjectTagsAPI": from ._generated.project_tags import ProjectTagsAPI + + return self._service("project_tags", ProjectTagsAPI) + + @property + def projects(self) -> "ProjectsAPI": from ._generated.projects import ProjectsAPI + + return self._service("projects", ProjectsAPI) + + @property + def prompts(self) -> "PromptsAPI": from ._generated.prompts import PromptsAPI + + return self._service("prompts", PromptsAPI) + + @property + def roles(self) -> "RolesAPI": from ._generated.roles import RolesAPI + + return self._service("roles", RolesAPI) + + @property + def service_tokens(self) -> "ServiceTokensAPI": from ._generated.service_tokens import ServiceTokensAPI + + return self._service("service_tokens", ServiceTokensAPI) + + @property + def span_iframes(self) -> "SpanIframesAPI": from ._generated.span_iframes import SpanIframesAPI + + return self._service("span_iframes", SpanIframesAPI) + + @property + def users(self) -> "UsersAPI": from ._generated.users import UsersAPI + + return self._service("users", UsersAPI) + + @property + def views(self) -> "ViewsAPI": from ._generated.views import ViewsAPI - service_args = (self.transport, self.router, api_key) - self.api_key = api_key - self.acls = AclsAPI(*service_args) - self.agents = AgentsAPI(*service_args) - self.ai_secrets = AiSecretsAPI(*service_args) - self.api_keys = ApiKeysAPI(*service_args) - self.dataset_snapshots = DatasetSnapshotsAPI(*service_args) - self.datasets = DatasetsAPI(*service_args) - self.env_vars = EnvVarsAPI(*service_args) - self.environments = EnvironmentsAPI(*service_args) - self.experiments = ExperimentsAPI(*service_args) - self.functions = FunctionsAPI(*service_args) - self.groups = GroupsAPI(*service_args) - self.mcp_servers = McpServersAPI(*service_args) - self.org_automations = OrgAutomationsAPI(*service_args) - self.organizations = OrganizationsAPI(*service_args) - self.project_automations = ProjectAutomationsAPI(*service_args) - self.project_groups = ProjectGroupsAPI(*service_args) - self.project_scores = ProjectScoresAPI(*service_args) - self.project_tags = ProjectTagsAPI(*service_args) - self.projects = ProjectsAPI(*service_args) - self.prompts = PromptsAPI(*service_args) - self.roles = RolesAPI(*service_args) - self.service_tokens = ServiceTokensAPI(*service_args) - self.span_iframes = SpanIframesAPI(*service_args) - self.users = UsersAPI(*service_args) - self.views = ViewsAPI(*service_args) + return self._service("views", ViewsAPI) def close(self) -> None: """Close the transport when it was created by this client.""" diff --git a/py/src/braintrust/api/test_generated_models.py b/py/src/braintrust/api/test_generated_models.py index 96d6ff07..b458e364 100644 --- a/py/src/braintrust/api/test_generated_models.py +++ b/py/src/braintrust/api/test_generated_models.py @@ -2,6 +2,7 @@ import json import subprocess import sys +import threading from typing import get_type_hints, is_typeddict @@ -72,6 +73,55 @@ def test_generated_models_import_on_supported_python(): assert get_type_hints(prompt_bindings.PromptsAPI.get_prompt)["return"] is models.GetPromptResponse +def test_openapi_client_lazily_loads_and_caches_generated_resources(): + script = """ +import json +import sys +from braintrust.api import BraintrustOpenApiClient + +client = BraintrustOpenApiClient(api_key="test-key", api_url="https://api.example.com") +before = sorted(name for name in sys.modules if name.startswith("braintrust.api._generated")) +first = client.projects +second = client.projects +after = sorted(name for name in sys.modules if name.startswith("braintrust.api._generated")) +print(json.dumps({"before": before, "cached": first is second, "after": after})) +client.close() +""" + + result = subprocess.run([sys.executable, "-c", script], check=True, capture_output=True, text=True) + loaded = json.loads(result.stdout) + + assert loaded["before"] == [] + assert loaded["cached"] is True + assert "braintrust.api._generated.projects" in loaded["after"] + assert "braintrust.api._generated.models.projects" in loaded["after"] + assert "braintrust.api._generated.datasets" not in loaded["after"] + assert "braintrust.api._generated.models.datasets" not in loaded["after"] + assert "braintrust.api._generated.models.project_automations" not in loaded["after"] + + +def test_openapi_client_caches_one_resource_across_threads(): + from braintrust.api import BraintrustOpenApiClient + + client = BraintrustOpenApiClient(api_key="test-key", api_url="https://api.example.com") + barrier = threading.Barrier(8) + resources = [] + + def load_projects(): + barrier.wait() + resources.append(client.projects) + + threads = [threading.Thread(target=load_projects) for _ in range(barrier.parties)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + client.close() + + assert len(resources) == barrier.parties + assert all(resource is resources[0] for resource in resources) + + def test_openapi_client_exposes_only_reviewed_generated_resources(): from braintrust.api import BraintrustOpenApiClient diff --git a/py/tests/api_codegen/test_generation.py b/py/tests/api_codegen/test_generation.py index 18b275df..d7cbb98a 100644 --- a/py/tests/api_codegen/test_generation.py +++ b/py/tests/api_codegen/test_generation.py @@ -117,23 +117,14 @@ def test_generated_tags_are_wired_to_openapi_client(): openapi_client = next( node for node in client_tree.body if isinstance(node, ast.ClassDef) and node.name == "BraintrustOpenApiClient" ) - initializer = next( - node + lazy_resources = { + node.name for node in openapi_client.body - if isinstance(node, ast.FunctionDef) and node.name == "_initialize_services" - ) - initialized_resources = { - target.attr - for node in initializer.body - if isinstance(node, ast.Assign) - for target in node.targets - if isinstance(target, ast.Attribute) - and isinstance(target.value, ast.Name) - and target.value.id == "self" - and target.attr != "api_key" + if isinstance(node, ast.FunctionDef) + and any(isinstance(decorator, ast.Name) and decorator.id == "property" for decorator in node.decorator_list) } - assert initialized_resources == expected_resources + assert lazy_resources == expected_resources def test_public_rest_types_match_generated_request_and_response_models(): @@ -335,8 +326,12 @@ def test_multiple_generated_resources_partition_shared_models_deterministically( assert "from .models.common import Widget" in (generated / "widgets.py").read_text() assert "from .models.gadgets import Gadget" in (generated / "gadgets.py").read_text() model_exports = (generated / "models" / "__init__.py").read_text() + assert "if TYPE_CHECKING:" in model_exports assert "from .common import Widget, WidgetDetails" in model_exports assert "from .gadgets import Gadget" in model_exports + assert '"Gadget": "gadgets"' in model_exports + assert '"Widget": "common"' in model_exports + assert "def __getattr__(name: str) -> Any:" in model_exports def test_unreachable_models_are_omitted_but_transitive_references_are_kept(tmp_path, codegen_config, minimal_spec):