diff --git a/codegen/in/archive/openapi_1.6.0+dev.e1f10d7ad5b.json b/codegen/in/archive/openapi_1.6.0+dev.e1f10d7ad5b.json new file mode 100644 index 000000000..eaa081738 --- /dev/null +++ b/codegen/in/archive/openapi_1.6.0+dev.e1f10d7ad5b.json @@ -0,0 +1,4356 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Aignostics Platform API", + "description": "\nThe Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. \n\nTo begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. \n\nMore information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com).\n\n**How to authorize and test API endpoints:**\n\n1. Click the \"Authorize\" button in the right corner below\n3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials\n4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint\n\n**Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized.\n\n", + "version": "1.6.0+dev.e1f10d7ad5b" + }, + "servers": [ + { + "url": "/api" + } + ], + "paths": { + "/v1/applications": { + "get": { + "tags": [ + "Public" + ], + "summary": "List available applications", + "description": "Returns the list of the applications, available to the caller.\n\nThe application is available if any of the versions of the application is assigned to the caller's organization.\nThe response is paginated and sorted according to the provided parameters.", + "operationId": "list_applications_v1_applications_get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page-size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 5, + "default": 50, + "title": "Page-Size" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order.\n\n**Available fields:**\n- `application_id`\n- `name`\n- `description`\n- `regulatory_classes`\n\n**Examples:**\n- `?sort=application_id` - Sort by application_id ascending\n- `?sort=-name` - Sort by name descending\n- `?sort=+description&sort=name` - Sort by description ascending, then name descending", + "title": "Sort" + }, + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order.\n\n**Available fields:**\n- `application_id`\n- `name`\n- `description`\n- `regulatory_classes`\n\n**Examples:**\n- `?sort=application_id` - Sort by application_id ascending\n- `?sort=-name` - Sort by name descending\n- `?sort=+description&sort=name` - Sort by description ascending, then name descending" + } + ], + "responses": { + "200": { + "description": "A list of applications available to the caller", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationReadShortResponse" + }, + "title": "Response List Applications V1 Applications Get" + }, + "example": [ + { + "application_id": "he-tme", + "name": "Atlas H&E-TME", + "regulatory_classes": [ + "RUO" + ], + "description": "The Atlas H&E TME is an AI application designed to examine FFPE (formalin-fixed, paraffin-embedded) tissues stained with H&E (hematoxylin and eosin), delivering comprehensive insights into the tumor microenvironment.", + "latest_version": { + "number": "1.0.0", + "released_at": "2025-09-01T19:01:05.401Z" + } + }, + { + "application_id": "test-app", + "name": "Test Application", + "regulatory_classes": [ + "RUO" + ], + "description": "This is the test application with two algorithms: TissueQc and Tissue Segmentation", + "latest_version": { + "number": "2.0.0", + "released_at": "2025-09-02T19:01:05.401Z" + } + } + ] + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/applications/{application_id}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Read Application By Id", + "description": "Retrieve details of a specific application by its ID.", + "operationId": "read_application_by_id_v1_applications__application_id__get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Application Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApplicationReadResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to see this application" + }, + "404": { + "description": "Not Found - Application with the given ID does not exist" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/applications/{application_id}/versions/{version}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Application Version Details", + "description": "Get the application version details.\n\nAllows caller to retrieve information about application version based on provided application version ID.", + "operationId": "application_version_details_v1_applications__application_id__versions__version__get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Application Id" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "title": "Version" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionReadResponse" + }, + "example": { + "version_number": "0.4.4", + "changelog": "New deployment", + "input_artifacts": [ + { + "name": "whole_slide_image", + "mime_type": "image/tiff", + "metadata_schema": { + "type": "object", + "$defs": { + "LungCancerMetadata": { + "type": "object", + "title": "LungCancerMetadata", + "required": [ + "type", + "tissue" + ], + "properties": { + "type": { + "enum": [ + "lung" + ], + "type": "string", + "const": "lung", + "title": "Type" + }, + "tissue": { + "enum": [ + "lung", + "lymph node", + "liver", + "adrenal gland", + "bone", + "brain" + ], + "type": "string", + "title": "Tissue" + } + }, + "additionalProperties": false + } + }, + "title": "ExternalImageMetadata", + "$schema": "http://json-schema.org/draft-07/schema#", + "required": [ + "checksum_crc32c", + "base_mpp", + "width", + "height", + "cancer" + ], + "properties": { + "stain": { + "enum": [ + "H&E" + ], + "type": "string", + "const": "H&E", + "title": "Stain", + "default": "H&E" + }, + "width": { + "type": "integer", + "title": "Width", + "maximum": 150000, + "minimum": 1 + }, + "cancer": { + "anyOf": [ + { + "$ref": "#/$defs/LungCancerMetadata" + } + ], + "title": "Cancer" + }, + "height": { + "type": "integer", + "title": "Height", + "maximum": 150000, + "minimum": 1 + }, + "base_mpp": { + "type": "number", + "title": "Base Mpp", + "maximum": 0.5, + "minimum": 0.125 + }, + "mime_type": { + "enum": [ + "application/dicom", + "image/tiff" + ], + "type": "string", + "title": "Mime Type", + "default": "image/tiff" + }, + "checksum_crc32c": { + "type": "string", + "title": "Checksum Crc32C" + } + }, + "description": "Metadata corresponding to an external image.", + "additionalProperties": false + } + } + ], + "output_artifacts": [ + { + "name": "tissue_qc:tiff_heatmap", + "mime_type": "image/tiff", + "metadata_schema": { + "type": "object", + "title": "HeatmapMetadata", + "$schema": "http://json-schema.org/draft-07/schema#", + "required": [ + "checksum_crc32c", + "width", + "height", + "class_colors" + ], + "properties": { + "width": { + "type": "integer", + "title": "Width" + }, + "height": { + "type": "integer", + "title": "Height" + }, + "base_mpp": { + "type": "number", + "title": "Base Mpp", + "maximum": 0.5, + "minimum": 0.125 + }, + "mime_type": { + "enum": [ + "image/tiff" + ], + "type": "string", + "const": "image/tiff", + "title": "Mime Type", + "default": "image/tiff" + }, + "class_colors": { + "type": "object", + "title": "Class Colors", + "additionalProperties": { + "type": "array", + "maxItems": 3, + "minItems": 3, + "prefixItems": [ + { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + { + "type": "integer", + "maximum": 255, + "minimum": 0 + } + ] + } + }, + "checksum_crc32c": { + "type": "string", + "title": "Checksum Crc32C" + } + }, + "description": "Metadata corresponding to a segmentation heatmap file.", + "additionalProperties": false + }, + "scope": "ITEM", + "visibility": "EXTERNAL" + } + ], + "released_at": "2025-04-16T08:45:20.655972Z" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to see this version" + }, + "404": { + "description": "Not Found - Application version with given ID is not available to you or does not exist" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs": { + "get": { + "tags": [ + "Public" + ], + "summary": "List Runs", + "description": "List runs with filtering, sorting, and pagination capabilities.\n\nReturns paginated runs that were submitted by the user.", + "operationId": "list_runs_v1_runs_get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Optional application ID filter", + "examples": [ + "tissue-segmentation", + "heta" + ], + "title": "Application Id" + }, + "description": "Optional application ID filter" + }, + { + "name": "application_version", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Optional Version Name", + "examples": [ + "1.0.2", + "1.0.1-beta2" + ], + "title": "Application Version" + }, + "description": "Optional Version Name" + }, + { + "name": "external_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Optionally filter runs by items with this external ID", + "examples": [ + "slide_001", + "patient_12345_sample_A" + ], + "title": "External Id" + }, + "description": "Optionally filter runs by items with this external ID" + }, + { + "name": "custom_metadata", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 1000 + }, + { + "type": "null" + } + ], + "description": "Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata.\n#### URL Encoding Required\n**Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding.\n\n#### Examples (Clear Format):\n- **Field existence**: `$.study` - Runs that have a study field defined\n- **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value\n- **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75\n- **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\"\n- **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements\n\n#### Examples (URL-Encoded Format):\n- **Field existence**: `%24.study`\n- **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)`\n- **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)`\n- **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)`\n- **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)`\n\n#### Notes\n- JSONPath expressions are evaluated using PostgreSQL's `@?` operator\n- The `$.` prefix is automatically added to root-level field references if missing\n- String values in conditions must be enclosed in double quotes\n- Use `&&` for AND operations and `||` for OR operations\n- Regular expressions use `like_regex` with standard regex syntax\n- **Please remember to URL-encode the entire JSONPath expression when making HTTP requests**\n\n ", + "title": "Custom Metadata" + }, + "description": "Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata.\n#### URL Encoding Required\n**Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding.\n\n#### Examples (Clear Format):\n- **Field existence**: `$.study` - Runs that have a study field defined\n- **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value\n- **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75\n- **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\"\n- **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements\n\n#### Examples (URL-Encoded Format):\n- **Field existence**: `%24.study`\n- **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)`\n- **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)`\n- **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)`\n- **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)`\n\n#### Notes\n- JSONPath expressions are evaluated using PostgreSQL's `@?` operator\n- The `$.` prefix is automatically added to root-level field references if missing\n- String values in conditions must be enclosed in double quotes\n- Use `&&` for AND operations and `||` for OR operations\n- Regular expressions use `like_regex` with standard regex syntax\n- **Please remember to URL-encode the entire JSONPath expression when making HTTP requests**\n\n ", + "examples": { + "no_filter": { + "summary": "No filter (returns all)", + "description": "Returns all items without filtering by custom metadata", + "value": "$" + }, + "field_exists": { + "summary": "Check if field exists", + "description": "Find applications that have a project field defined", + "value": "$.study" + }, + "field_has_value": { + "summary": "Check if field has a certain value", + "description": "Compare a field value against a certain value", + "value": "$.study ? (@ == \"abc-1\")" + }, + "numeric_comparisons": { + "summary": "Compare to a numeric value of a field", + "description": "Compare a field value against a numeric value of a field", + "value": "$.confidence_score ? (@ > 0.75)" + }, + "array_operations": { + "summary": "Check if an array contains a certain value", + "description": "Check if an array contains a certain value", + "value": "$.tags[*] ? (@ == \"draft\")" + }, + "complex_filters": { + "summary": "Combine multiple checks", + "description": "Combine multiple checks", + "value": "$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)" + } + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 5, + "default": 50, + "title": "Page Size" + } + }, + { + "name": "submitted_by", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user.", + "examples": [ + "me", + "auth0|123456789" + ], + "title": "Submitted By" + }, + "description": "Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user." + }, + { + "name": "organization_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization.", + "examples": [ + "my_org", + "org_acme" + ], + "title": "Organization Id" + }, + "description": "Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization." + }, + { + "name": "for_organization", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs.", + "title": "For Organization" + }, + "description": "Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs." + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order.\n\n**Available fields:**\n- `run_id`\n- `application_id`\n- `version_number`\n- `custom_metadata`\n- `submitted_at`\n- `submitted_by`\n- `terminated_at`\n- `termination_reason`\n\n**Examples:**\n- `?sort=submitted_at` - Sort by creation time (ascending)\n- `?sort=-submitted_at` - Sort by creation time (descending)\n- `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending)\n", + "title": "Sort" + }, + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order.\n\n**Available fields:**\n- `run_id`\n- `application_id`\n- `version_number`\n- `custom_metadata`\n- `submitted_at`\n- `submitted_by`\n- `terminated_at`\n- `termination_reason`\n\n**Examples:**\n- `?sort=submitted_at` - Sort by creation time (ascending)\n- `?sort=-submitted_at` - Sort by creation time (descending)\n- `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending)\n" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RunReadResponse" + }, + "title": "Response List Runs V1 Runs Get" + } + } + } + }, + "404": { + "description": "Run not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "Public" + ], + "summary": "Initiate Run", + "description": "This endpoint initiates a processing run for a selected application and version, and returns a `run_id` for tracking purposes.\n\nSlide processing occurs asynchronously, allowing you to retrieve results for individual slides as soon as they\ncomplete processing. The system typically processes slides in batches.\nBelow is an example of the required payload for initiating an Atlas H&E TME processing run.\n\n\n### Payload\n\nThe payload includes `application_id`, optional `version_number`, and `items` base fields.\n\n`application_id` is the unique identifier for the application.\n`version_number` is the semantic version to use. If not provided, the latest available version will be used.\n\n`items` includes the list of the items to process (slides, in case of HETA application).\nEvery item has a set of standard fields defined by the API, plus the custom_metadata, specific to the\nchosen application.\n\nExample payload structure with the comments:\n```\n{\n application_id: \"he-tme\",\n version_number: \"1.0.0-beta\",\n items: [{\n \"external_id\": \"slide_1\",\n \"custom_metadata\": {\"project\": \"sample-study\"},\n \"input_artifacts\": [{\n \"name\": \"user_slide\",\n \"download_url\": \"https://...\",\n \"metadata\": {\n \"specimen\": {\n \"disease\": \"LUNG_CANCER\",\n \"tissue\": \"LUNG\"\n },\n \"staining_method\": \"H&E\",\n \"width_px\": 136223,\n \"height_px\": 87761,\n \"resolution_mpp\": 0.2628238,\n \"media-type\":\"image/tiff\",\n \"checksum_base64_crc32c\": \"64RKKA==\"\n }\n }]\n }]\n}\n```\n\n| Parameter | Description |\n| :---- | :---- |\n| `application_id` required | Unique ID for the application |\n| `version_number` optional | Semantic version of the application. If not provided, the latest available version will be used |\n| `items` required | List of submitted items i.e. whole slide images (WSIs) with parameters described below. |\n| `external_id` required | Unique WSI name or ID for easy reference to items, provided by the caller. The `external_id` should be unique across all items of the run. |\n| `input_artifacts` required | List of provided artifacts for a WSI; at the moment Atlas H&E-TME receives only 1 artifact per slide (the slide itself), but for some other applications this can be a slide and a segmentation map |\n| `name` required | Type of artifact; Atlas H&E-TME supports only `\"input_slide\"` |\n| `download_url` required | Signed URL to the input file in the S3 or GCS; Should be valid for at least 6 days |\n| `specimen: disease` required | Supported cancer types for Atlas H&E-TME (see full list in Atlas H&E-TME manual) |\n| `specimen: tissue` required | Supported tissue types for Atlas H&E-TME (see full list in Atlas H&E-TME manual) |\n| `staining_method` required | WSI stain bio-marker; Atlas H&E-TME supports only `\"H&E\"` |\n| `width_px` required | Integer value. Number of pixels of the WSI in the X dimension. |\n| `height_px` required | Integer value. Number of pixels of the WSI in the Y dimension. |\n| `resolution_mpp` required | Resolution of WSI in micrometers per pixel; check allowed range in Atlas H&E-TME manual |\n| `media-type` required | Supported media formats; available values are: image/tiff (for .tiff or .tif WSI), application/dicom (for DICOM ), application/zip (for zipped DICOM), and application/octet-stream (for .svs WSI) |\n| `checksum_base64_crc32c` required | Base64-encoded big-endian CRC32C checksum of the WSI image |\n\n\n\n### Response\n\nThe endpoint returns the run UUID. After that, the job is scheduled for the execution in the background.\n\nTo check the status of the run, call `GET v1/runs/{run_id}` endpoint with the returned run UUID.\n\n### Rejection\n\nApart from the authentication, authorization, and malformed input error, the request can be\nrejected when specific quota limit is exceeded. More details on quotas is described in the\ndocumentation", + "operationId": "create_run_v1_runs_post", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunCreationRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunCreationResponse" + } + } + } + }, + "404": { + "description": "Application version not found" + }, + "403": { + "description": "Forbidden - You don't have permission to create this run" + }, + "400": { + "description": "Bad Request - Input validation failed" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get run details", + "description": "This endpoint allows the caller to retrieve the current status of a run along with other relevant run details.\n A run becomes available immediately after it is created through the `POST /v1/runs/` endpoint.\n\n To download the output results, use `GET /v1/runs/{run_id}/` items to get outputs for all slides.\nAccess to a run is restricted to the user who created it, or users with an active grant or valid share token.", + "operationId": "get_run_v1_runs__run_id__get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Run id, returned by `POST /v1/runs/` endpoint", + "title": "Run Id" + }, + "description": "Run id, returned by `POST /v1/runs/` endpoint" + }, + { + "name": "share_token", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Share token for accessing shared runs", + "title": "Share Token" + }, + "description": "Share token for accessing shared runs" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunReadResponse" + } + } + } + }, + "404": { + "description": "Run not found because it was deleted." + }, + "403": { + "description": "Forbidden - You don't have permission to see this run" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/cancel": { + "post": { + "tags": [ + "Public" + ], + "summary": "Cancel Run", + "description": "The run can be canceled by the user who created the run.\n\nThe execution can be canceled any time while the run is not in the terminated state. The\npending items of a canceled run will not be processed and will not add to the cost.\n\nWhen the run is canceled, the already completed items remain available for download.", + "operationId": "cancel_run_v1_runs__run_id__cancel_post", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Run id, returned by `POST /runs/` endpoint", + "title": "Run Id" + }, + "description": "Run id, returned by `POST /runs/` endpoint" + } + ], + "responses": { + "202": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "404": { + "description": "Run not found" + }, + "403": { + "description": "Forbidden - You don't have permission to cancel this run" + }, + "409": { + "description": "Conflict - The Run is already cancelled" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/items": { + "get": { + "tags": [ + "Public" + ], + "summary": "List Run Items", + "description": "List items in a run with filtering, sorting, and pagination capabilities.\n\nReturns paginated items within a specific run. Results can be filtered\nby `item_id`, `external_ids`, `custom_metadata`, `terminated_at`, and `termination_reason` using JSONPath expressions.\n\n## JSONPath Metadata Filtering\nUse PostgreSQL JSONPath expressions to filter items using their custom_metadata.\n\n### Examples:\n- **Field existence**: `$.case_id` - Results that have a case_id field defined\n- **Exact value match**: `$.priority ? (@ == \"high\")` - Results with high priority\n- **Numeric comparison**: `$.confidence_score ? (@ > 0.95)` - Results with high confidence\n- **Array operations**: `$.flags[*] ? (@ == \"reviewed\")` - Results flagged as reviewed\n- **Complex conditions**: `$.metrics ? (@.accuracy > 0.9 && @.recall > 0.8)` - Results meeting performance thresholds\n\n## Notes\n- JSONPath expressions are evaluated using PostgreSQL's `@?` operator\n- The `$.` prefix is automatically added to root-level field references if missing\n- String values in conditions must be enclosed in double quotes\n- Use `&&` for AND operations and `||` for OR operations", + "operationId": "list_run_items_v1_runs__run_id__items_get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Run id, returned by `POST /v1/runs/` endpoint", + "title": "Run Id" + }, + "description": "Run id, returned by `POST /v1/runs/` endpoint" + }, + { + "name": "share_token", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Share token for accessing shared runs", + "title": "Share Token" + }, + "description": "Share token for accessing shared runs" + }, + { + "name": "item_id__in", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + { + "type": "null" + } + ], + "description": "Filter for item ids", + "title": "Item Id In" + }, + "description": "Filter for item ids" + }, + { + "name": "external_id__in", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Filter for items by their external_id from the input payload", + "title": "External Id In" + }, + "description": "Filter for items by their external_id from the input payload" + }, + { + "name": "state", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ItemState" + }, + { + "type": "null" + } + ], + "description": "Filter items by their state", + "title": "State" + }, + "description": "Filter items by their state" + }, + { + "name": "termination_reason", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ItemTerminationReason" + }, + { + "type": "null" + } + ], + "description": "Filter items by their termination reason. Only applies to TERMINATED items.", + "title": "Termination Reason" + }, + "description": "Filter items by their termination reason. Only applies to TERMINATED items." + }, + { + "name": "custom_metadata", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 1000 + }, + { + "type": "null" + } + ], + "description": "JSONPath expression to filter items by their custom_metadata", + "title": "Custom Metadata" + }, + "description": "JSONPath expression to filter items by their custom_metadata", + "examples": { + "no_filter": { + "summary": "No filter (returns all)", + "description": "Returns all items without filtering by custom metadata", + "value": "$" + }, + "field_exists": { + "summary": "Check if field exists", + "description": "Find items that have a project field defined", + "value": "$.project" + }, + "field_has_value": { + "summary": "Check if field has a certain value", + "description": "Compare a field value against a certain value", + "value": "$.project ? (@ == \"cancer-research\")" + }, + "numeric_comparisons": { + "summary": "Compare to a numeric value of a field", + "description": "Compare a field value against a numeric value of a field", + "value": "$.duration_hours ? (@ < 2)" + }, + "array_operations": { + "summary": "Check if an array contains a certain value", + "description": "Check if an array contains a certain value", + "value": "$.tags[*] ? (@ == \"production\")" + }, + "complex_filters": { + "summary": "Combine multiple checks", + "description": "Combine multiple checks", + "value": "$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)" + } + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 5, + "default": 50, + "title": "Page Size" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Sort the items by one or more fields. Use `+` for ascending and `-` for descending order.\n **Available fields:**\n- `item_id`\n- `external_id`\n- `custom_metadata`\n- `terminated_at`\n- `termination_reason`\n\n**Examples:**\n- `?sort=item_id` - Sort by id of the item (ascending)\n- `?sort=-external_id` - Sort by external ID (descending)\n- `?sort=custom_metadata&sort=-external_id` - Sort by metadata, then by external ID (descending)", + "title": "Sort" + }, + "description": "Sort the items by one or more fields. Use `+` for ascending and `-` for descending order.\n **Available fields:**\n- `item_id`\n- `external_id`\n- `custom_metadata`\n- `terminated_at`\n- `termination_reason`\n\n**Examples:**\n- `?sort=item_id` - Sort by id of the item (ascending)\n- `?sort=-external_id` - Sort by external ID (descending)\n- `?sort=custom_metadata&sort=-external_id` - Sort by metadata, then by external ID (descending)" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemResultReadResponse" + }, + "title": "Response List Run Items V1 Runs Run Id Items Get" + } + } + } + }, + "404": { + "description": "Run not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/items/{external_id}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get Item By Run", + "description": "Retrieve details of a specific item (slide) by its external ID and the run ID.", + "operationId": "get_item_by_run_v1_runs__run_id__items__external_id__get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "The run id, returned by `POST /runs/` endpoint", + "title": "Run Id" + }, + "description": "The run id, returned by `POST /runs/` endpoint" + }, + { + "name": "external_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "The `external_id` that was defined for the item by the customer that triggered the run.", + "title": "External Id" + }, + "description": "The `external_id` that was defined for the item by the customer that triggered the run." + }, + { + "name": "share_token", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Share token for accessing shared runs", + "title": "Share Token" + }, + "description": "Share token for accessing shared runs" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ItemResultReadResponse" + } + } + } + }, + "404": { + "description": "Not Found - Item with given ID does not exist" + }, + "403": { + "description": "Forbidden - You don't have permission to see this item" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/artifacts/{artifact_id}/file": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get Artifact Url", + "description": "Download the artifact file with the specified artifact_id, belonging to the specified run.\nThe artifact_is is returned by the `GET /v1/runs/{run_id}/items` endpoint as part of the item results, and can also\nbe retrieved via `GET /v1/runs/{run_id}/items/{external_id}`.\n\nThe endpoint may return a redirect response with a presigned URL to download the artifact file from the storage\nbucket. The presigned URL is valid for a limited time, so it should be used immediately after receiving the response.", + "operationId": "get_artifact_url_v1_runs__run_id__artifacts__artifact_id__file_get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Run id, returned by `POST /runs/` endpoint", + "title": "Run Id" + }, + "description": "Run id, returned by `POST /runs/` endpoint" + }, + { + "name": "artifact_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "The artifact id to download", + "title": "Artifact Id" + }, + "description": "The artifact id to download" + }, + { + "name": "share_token", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Share token for accessing shared runs", + "title": "Share Token" + }, + "description": "Share token for accessing shared runs" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "404": { + "description": "Not Found - Artifact not found for the specified run" + }, + "307": { + "description": "Temporary Redirect - Redirect to the artifact file URL" + }, + "403": { + "description": "Forbidden - You don't have permission to download this artifact" + }, + "410": { + "description": "Gone - Artifact has been deleted" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/artifacts": { + "delete": { + "tags": [ + "Public" + ], + "summary": "Delete Run Items", + "description": "This endpoint allows the caller to explicitly delete artifacts generated by a run.\nIt can only be invoked when the run has reached a final state, i.e.\n`PROCESSED`, `CANCELED_SYSTEM`, or `CANCELED_USER`.\nNote that by default, all artifacts are automatically deleted 30 days after the run finishes,\nregardless of whether the caller explicitly requests such deletion.", + "operationId": "delete_run_items_v1_runs__run_id__artifacts_delete", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Run id, returned by `POST /runs/` endpoint", + "title": "Run Id" + }, + "description": "Run id, returned by `POST /runs/` endpoint" + } + ], + "responses": { + "200": { + "description": "Run artifacts deleted", + "content": { + "application/json": { + "schema": {} + } + } + }, + "404": { + "description": "Run not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/custom-metadata": { + "put": { + "tags": [ + "Public" + ], + "summary": "Put Run Custom Metadata", + "description": "Update the custom metadata of a run with the specified `run_id`.\n\nOptionally, a checksum may be provided along the custom metadata JSON.\nIt can be used to verify if the custom metadata was updated since the last time it was accessed.\nIf the checksum is provided, it must match the existing custom metadata in the system, ensuring that the current\ncustom metadata value to be overwritten is acknowledged by the user.\nIf no checksum is provided, submitted metadata directly overwrites the existing metadata, without any checks.\n\nThe latest custom metadata and checksum can be retrieved for the run via the `GET /v1/runs/{run_id}` endpoint.\n\n**Note on deadlines:** Run deadlines must be set during run creation and cannot be modified afterward.\nAny deadline changes in custom metadata will be ignored by the system.", + "operationId": "put_run_custom_metadata_v1_runs__run_id__custom_metadata_put", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Run id, returned by `POST /runs/` endpoint", + "title": "Run Id" + }, + "description": "Run id, returned by `POST /runs/` endpoint" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomMetadataUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Custom metadata successfully updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomMetadataUpdateResponse" + } + } + } + }, + "404": { + "description": "Run not found" + }, + "403": { + "description": "Forbidden - You don't have permission to update this run" + }, + "412": { + "description": "Precondition Failed - Checksum mismatch, resource has been modified" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/runs/{run_id}/items/{external_id}/custom-metadata": { + "put": { + "tags": [ + "Public" + ], + "summary": "Put Item Custom Metadata By Run", + "description": "Update the custom metadata of the item with the specified `external_id`, belonging to the specified run.\n\nOptionally, a checksum may be provided along the custom metadata JSON.\nIt can be used to verify if the custom metadata was updated since the last time it was accessed.\nIf the checksum is provided, it must match the existing custom metadata in the system, ensuring that the current\ncustom metadata value to be overwritten is acknowledged by the user.\nIf no checksum is provided, submitted metadata directly overwrites the existing metadata, without any checks.\n\nThe latest custom metadata and checksum can be retrieved\n for individual items via `GET /v1/runs/{run_id}/items/{external_id}`,\n and for all items of a run via `GET /v1/runs/{run_id}/items`.", + "operationId": "put_item_custom_metadata_by_run_v1_runs__run_id__items__external_id__custom_metadata_put", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "The run id, returned by `POST /runs/` endpoint", + "title": "Run Id" + }, + "description": "The run id, returned by `POST /runs/` endpoint" + }, + { + "name": "external_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "The `external_id` that was defined for the item by the customer that triggered the run.", + "title": "External Id" + }, + "description": "The `external_id` that was defined for the item by the customer that triggered the run." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomMetadataUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Custom metadata successfully updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomMetadataUpdateResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to update this item" + }, + "404": { + "description": "Item not found" + }, + "412": { + "description": "Precondition Failed - Checksum mismatch" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/me": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get current user", + "description": "Retrieves your identity details, including name, email, and organization.\nThis is useful for verifying that the request is being made under the correct user profile\nand organization context, as well as confirming that the expected environment variables are correctly set\n(in case you are using Python SDK)", + "operationId": "get_me_v1_me_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeReadResponse" + } + } + } + } + }, + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ] + } + }, + "/v1/applications/{application_id}/versions/{version}/documents": { + "get": { + "tags": [ + "Public" + ], + "summary": "List version documents", + "description": "List public documents attached to an application version.\n\nReturns only documents with ``visibility=public`` and ``status=uploaded``.", + "operationId": "list_version_documents", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Application Id" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Version" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionDocumentResponse" + }, + "title": "Response List Version Documents" + } + } + } + }, + "404": { + "description": "Application version not found or not accessible" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/applications/{application_id}/versions/{version}/documents/{name}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get version document metadata", + "description": "Return metadata for a single public document attached to an application version.", + "operationId": "get_version_document", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Application Id" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Version" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionDocumentResponse" + } + } + } + }, + "404": { + "description": "Document not found, not public, or version not accessible" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/applications/{application_id}/versions/{version}/documents/{name}/file": { + "get": { + "tags": [ + "Public" + ], + "summary": "Download version document (browser)", + "description": "307 redirect to a short-lived GCS signed URL for downloading a document.\n\nThe signed URL includes ``response-content-disposition=attachment; filename=\"\"``\nso browsers prompt a save-as dialog rather than rendering inline.\nResponse carries ``Cache-Control: no-store``.", + "operationId": "get_version_document_file", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Application Id" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Version" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "307": { + "description": "Temporary redirect to signed GCS URL with Content-Disposition: attachment" + }, + "404": { + "description": "Document not found, not public, or version not accessible" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/applications/{application_id}/versions/{version}/documents/{name}/content": { + "get": { + "tags": [ + "Public" + ], + "summary": "Stream version document content (programmatic)", + "description": "307 redirect to a short-lived GCS signed URL for streaming document content.\n\nUnlike ``/file``, no ``Content-Disposition`` override is set — GCS serves\nthe object body with its stored ``Content-Type``. Intended for programmatic\nclients that follow redirects and consume the content directly.\nResponse carries ``Cache-Control: no-store``.", + "operationId": "get_version_document_content", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Application Id" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Version" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "307": { + "description": "Temporary redirect to signed GCS URL; GCS serves the object with its stored Content-Type" + }, + "404": { + "description": "Document not found, not public, or version not accessible" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/access/grants": { + "post": { + "tags": [ + "Public" + ], + "summary": "Create Grant", + "description": "Create a grant to share access to a resource with a subject (user or organization).", + "operationId": "create_grant_v1_access_grants_post", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GrantCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GrantReadResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to grant access to this resource" + }, + "404": { + "description": "Resource not found" + }, + "422": { + "description": "Unprocessable Entity - Only viewer grants can be created" + } + } + }, + "get": { + "tags": [ + "Public" + ], + "summary": "List Grants", + "description": "List grants.\n\nOrg admins see all grants for all resources in their organization.\nRegular users see grants for all resources they submitted.", + "operationId": "list_grants_v1_access_grants_get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "resource_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ResourceType" + }, + { + "type": "null" + } + ], + "title": "Resource Type" + } + }, + { + "name": "resource_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Resource Id" + } + }, + { + "name": "subject_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SubjectType" + }, + { + "type": "null" + } + ], + "title": "Subject Type" + } + }, + { + "name": "subject_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subject Id" + } + }, + { + "name": "relation", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/GrantRelation" + } + }, + { + "type": "null" + } + ], + "description": "Filter grants by relation type. Can be specified multiple times.", + "title": "Relation" + }, + "description": "Filter grants by relation type. Can be specified multiple times." + }, + { + "name": "revoked", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Revoked" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 5, + "default": 50, + "title": "Page Size" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order.", + "title": "Sort" + }, + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order." + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GrantReadResponse" + }, + "title": "Response List Grants V1 Access Grants Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/access/grants/{grant_id}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get Grant", + "description": "Get a grant by its ID.", + "operationId": "get_grant_v1_access_grants__grant_id__get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "grant_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Grant ID", + "title": "Grant Id" + }, + "description": "Grant ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GrantReadResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to view this grant" + }, + "404": { + "description": "Grant not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Public" + ], + "summary": "Revoke Grant", + "description": "Revoke a grant by its ID. Sets the revoked_at timestamp on the grant.", + "operationId": "revoke_grant_v1_access_grants__grant_id__delete", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "grant_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Grant ID", + "title": "Grant Id" + }, + "description": "Grant ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GrantReadResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to revoke this grant" + }, + "404": { + "description": "Grant not found" + }, + "409": { + "description": "Conflict - Grant is already revoked" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/access/share-tokens": { + "post": { + "tags": [ + "Public" + ], + "summary": "Create Share Token", + "description": "Create a share token. The returned share_token value is shown only once and is never stored.\nUse POST /access/grants with subject_type=share_token to grant access to a resource.", + "operationId": "create_share_token_v1_access_share_tokens_post", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareTokenCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareTokenCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "tags": [ + "Public" + ], + "summary": "List Share Tokens", + "description": "List share tokens. Service and Superadmin see all tokens; other users see only their own.", + "operationId": "list_share_tokens_v1_access_share_tokens_get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "run_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "Filter by run ID", + "title": "Run Id" + }, + "description": "Filter by run ID" + }, + { + "name": "created_by", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by share token creator", + "title": "Created By" + }, + "description": "Filter by share token creator" + }, + { + "name": "revoked", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Revoked" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 5, + "default": 50, + "title": "Page Size" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ], + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order.", + "title": "Sort" + }, + "description": "Sort the results by one or more fields. Use `+` for ascending and `-` for descending order." + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareTokenReadResponse" + }, + "title": "Response List Share Tokens V1 Access Share Tokens Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/access/share-tokens/{share_token_id}": { + "get": { + "tags": [ + "Public" + ], + "summary": "Get Share Token", + "description": "Get a share token by its ID.", + "operationId": "get_share_token_v1_access_share_tokens__share_token_id__get", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "share_token_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Share token ID", + "title": "Share Token Id" + }, + "description": "Share token ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareTokenReadResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to view this share token" + }, + "404": { + "description": "Share token not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Public" + ], + "summary": "Revoke Share Token", + "description": "Revoke a share token by its ID. Invalidates the credential regardless of any active grants.", + "operationId": "revoke_share_token_v1_access_share_tokens__share_token_id__delete", + "security": [ + { + "OAuth2AuthorizationCodeBearer": [] + } + ], + "parameters": [ + { + "name": "share_token_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "description": "Share token ID", + "title": "Share Token Id" + }, + "description": "Share token ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareTokenReadResponse" + } + } + } + }, + "403": { + "description": "Forbidden - You don't have permission to revoke this share token" + }, + "404": { + "description": "Share token not found" + }, + "409": { + "description": "Conflict - Share token is already revoked" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ApplicationReadResponse": { + "properties": { + "application_id": { + "type": "string", + "title": "Application Id", + "description": "Application ID", + "examples": [ + "he-tme" + ] + }, + "name": { + "type": "string", + "title": "Name", + "description": "Application display name", + "examples": [ + "Atlas H&E-TME" + ] + }, + "regulatory_classes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Regulatory Classes", + "description": "Regulatory classes, to which the applications comply with. Possible values include: RUO, IVDR, FDA.", + "examples": [ + [ + "RUO" + ] + ] + }, + "description": { + "type": "string", + "title": "Description", + "description": "Describing what the application can do ", + "examples": [ + "The Atlas H&E TME is an AI application designed to examine FFPE (formalin-fixed, paraffin-embedded) tissues stained with H&E (hematoxylin and eosin), delivering comprehensive insights into the tumor microenvironment." + ] + }, + "versions": { + "items": { + "$ref": "#/components/schemas/ApplicationVersion" + }, + "type": "array", + "title": "Versions", + "description": "All version numbers available to the user" + } + }, + "type": "object", + "required": [ + "application_id", + "name", + "regulatory_classes", + "description", + "versions" + ], + "title": "ApplicationReadResponse", + "description": "Response schema for `List available applications` and `Read Application by Id` endpoints" + }, + "ApplicationReadShortResponse": { + "properties": { + "application_id": { + "type": "string", + "title": "Application Id", + "description": "Application ID", + "examples": [ + "he-tme" + ] + }, + "name": { + "type": "string", + "title": "Name", + "description": "Application display name", + "examples": [ + "Atlas H&E-TME" + ] + }, + "regulatory_classes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Regulatory Classes", + "description": "Regulatory classes, to which the applications comply with. Possible values include: RUO, IVDR, FDA.", + "examples": [ + [ + "RUO" + ] + ] + }, + "description": { + "type": "string", + "title": "Description", + "description": "Describing what the application can do ", + "examples": [ + "The Atlas H&E TME is an AI application designed to examine FFPE (formalin-fixed, paraffin-embedded) tissues stained with H&E (hematoxylin and eosin), delivering comprehensive insights into the tumor microenvironment." + ] + }, + "latest_version": { + "anyOf": [ + { + "$ref": "#/components/schemas/ApplicationVersion" + }, + { + "type": "null" + } + ], + "description": "The version with highest version number available to the user" + } + }, + "type": "object", + "required": [ + "application_id", + "name", + "regulatory_classes", + "description" + ], + "title": "ApplicationReadShortResponse", + "description": "Response schema for `List available applications` and `Read Application by Id` endpoints" + }, + "ApplicationVersion": { + "properties": { + "number": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "title": "Number", + "description": "The number of the latest version", + "examples": [ + "1.0.0" + ] + }, + "released_at": { + "type": "string", + "format": "date-time", + "title": "Released At", + "description": "The timestamp for when the application version was made available in the Platform", + "examples": [ + "2025-09-15T10:30:45.123Z" + ] + } + }, + "type": "object", + "required": [ + "number", + "released_at" + ], + "title": "ApplicationVersion" + }, + "ArtifactOutput": { + "type": "string", + "enum": [ + "NONE", + "AVAILABLE", + "DELETED_BY_USER", + "DELETED_BY_SYSTEM" + ], + "title": "ArtifactOutput" + }, + "ArtifactState": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "TERMINATED" + ], + "title": "ArtifactState" + }, + "ArtifactTerminationReason": { + "type": "string", + "enum": [ + "SUCCEEDED", + "USER_ERROR", + "SYSTEM_ERROR", + "SKIPPED" + ], + "title": "ArtifactTerminationReason" + }, + "CustomMetadataUpdateRequest": { + "properties": { + "custom_metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata", + "description": "JSON metadata that should be set for the run", + "examples": [ + { + "department": "D1", + "study": "abc-1" + } + ] + }, + "custom_metadata_checksum": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata Checksum", + "description": "Optional field to verify that the latest custom metadata was known. If set to the checksum retrieved via the /runs endpoint, it must match the checksum of the current value in the database.", + "examples": [ + "f54fe109" + ] + } + }, + "type": "object", + "title": "CustomMetadataUpdateRequest" + }, + "CustomMetadataUpdateResponse": { + "properties": { + "custom_metadata_checksum": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata Checksum", + "description": "The checksum of the updated custom metadata. If the `custom_metadata` is None,\nthe checksum also None.", + "readOnly": true + } + }, + "type": "object", + "required": [ + "custom_metadata_checksum" + ], + "title": "CustomMetadataUpdateResponse" + }, + "GrantCreateRequest": { + "properties": { + "resource_type": { + "$ref": "#/components/schemas/ResourceType" + }, + "resource_id": { + "type": "string", + "format": "uuid", + "title": "Resource Id" + }, + "subject_type": { + "$ref": "#/components/schemas/SubjectType" + }, + "subject_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subject Id" + }, + "subject_email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subject Email" + }, + "relation": { + "$ref": "#/components/schemas/GrantRelation" + } + }, + "type": "object", + "required": [ + "resource_type", + "resource_id", + "subject_type", + "relation" + ], + "title": "GrantCreateRequest" + }, + "GrantReadResponse": { + "properties": { + "grant_id": { + "type": "string", + "format": "uuid", + "title": "Grant Id" + }, + "resource_type": { + "$ref": "#/components/schemas/ResourceType" + }, + "resource_id": { + "type": "string", + "format": "uuid", + "title": "Resource Id" + }, + "subject_type": { + "$ref": "#/components/schemas/SubjectType" + }, + "subject_id": { + "type": "string", + "title": "Subject Id" + }, + "relation": { + "$ref": "#/components/schemas/GrantRelation" + }, + "created_by": { + "type": "string", + "title": "Created By" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "revoked": { + "type": "boolean", + "title": "Revoked" + } + }, + "type": "object", + "required": [ + "grant_id", + "resource_type", + "resource_id", + "subject_type", + "subject_id", + "relation", + "created_by", + "created_at", + "revoked" + ], + "title": "GrantReadResponse" + }, + "GrantRelation": { + "type": "string", + "enum": [ + "owner", + "editor", + "viewer" + ], + "title": "GrantRelation" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "InputArtifact": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "mime_type": { + "type": "string", + "pattern": "^\\w+\\/\\w+[-+.|\\w+]+\\w+$", + "title": "Mime Type", + "examples": [ + "image/tiff" + ] + }, + "metadata_schema": { + "additionalProperties": true, + "type": "object", + "title": "Metadata Schema" + } + }, + "type": "object", + "required": [ + "name", + "mime_type", + "metadata_schema" + ], + "title": "InputArtifact" + }, + "InputArtifactCreationRequest": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Type of artifact. For Atlas H&E-TME, use \"input_slide\"", + "examples": [ + "input_slide" + ] + }, + "download_url": { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri", + "title": "Download Url", + "description": "[Signed URL](https://cloud.google.com/cdn/docs/using-signed-urls) to the input artifact file. The URL should be valid for at least 6 days from the payload submission time.", + "examples": [ + "https://example.com/case-no-1-slide.tiff" + ] + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata", + "description": "The metadata of the artifact, required by the application version. The JSON schema of the metadata can be requested by `/v1/versions/{application_version_id}`. The schema is located in `input_artifacts.[].metadata_schema`", + "examples": [ + { + "checksum_base64_crc32c": "752f9554", + "height": 2000, + "height_mpp": 0.5, + "width": 10000, + "width_mpp": 0.5 + } + ] + } + }, + "type": "object", + "required": [ + "name", + "download_url", + "metadata" + ], + "title": "InputArtifactCreationRequest", + "description": "Input artifact containing the slide image and associated metadata." + }, + "InputArtifactResultReadResponse": { + "properties": { + "input_artifact_id": { + "type": "string", + "format": "uuid", + "title": "Input Artifact Id", + "description": "The Id of the artifact. Used internally" + }, + "name": { + "type": "string", + "title": "Name", + "description": "Name of the input from the schema from the `/v1/versions/{version_id}` endpoint.", + "examples": [ + "whole_slide_image" + ] + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "description": "The metadata of the input artifact, provided by the user." + }, + "download_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + { + "type": "null" + } + ], + "title": "Download Url", + "description": "The download URL to for the input artifact provided by the user." + } + }, + "type": "object", + "required": [ + "input_artifact_id", + "name" + ], + "title": "InputArtifactResultReadResponse" + }, + "ItemCreationRequest": { + "properties": { + "external_id": { + "type": "string", + "maxLength": 255, + "title": "External Id", + "description": "Unique identifier for this item within the run. Used for referencing items. Must be unique across all items in the same run", + "examples": [ + "slide_1", + "patient_001_slide_A", + "sample_12345" + ] + }, + "custom_metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata", + "description": "Optional JSON custom_metadata to store additional information alongside an item.", + "examples": [ + { + "case": "abc" + } + ] + }, + "input_artifacts": { + "items": { + "$ref": "#/components/schemas/InputArtifactCreationRequest" + }, + "type": "array", + "title": "Input Artifacts", + "description": "List of input artifacts for this item. For Atlas H&E-TME, typically contains one artifact (the slide image)", + "examples": [ + [ + { + "download_url": "https://example-bucket.s3.amazonaws.com/slide1.tiff", + "metadata": { + "checksum_base64_crc32c": "64RKKA==", + "height_px": 87761, + "media-type": "image/tiff", + "resolution_mpp": 0.2628238, + "specimen": { + "disease": "LUNG_CANCER", + "tissue": "LUNG" + }, + "staining_method": "H&E", + "width_px": 136223 + }, + "name": "input_slide" + } + ] + ] + } + }, + "type": "object", + "required": [ + "external_id", + "input_artifacts" + ], + "title": "ItemCreationRequest", + "description": "Individual item (slide) to be processed in a run." + }, + "ItemOutput": { + "type": "string", + "enum": [ + "NONE", + "FULL" + ], + "title": "ItemOutput" + }, + "ItemResultReadResponse": { + "properties": { + "item_id": { + "type": "string", + "format": "uuid", + "title": "Item Id", + "description": "Item UUID generated by the Platform" + }, + "external_id": { + "type": "string", + "title": "External Id", + "description": "The external_id of the item from the user payload", + "examples": [ + "slide_1" + ] + }, + "custom_metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata", + "description": "The custom_metadata of the item that has been provided by the user on run creation." + }, + "custom_metadata_checksum": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata Checksum", + "description": "The checksum of the `custom_metadata` field.\nCan be used in the `PUT /runs/{run-id}/items/{external_id}/custom_metadata`\nrequest to avoid unwanted override of the values in concurrent requests.", + "examples": [ + "f54fe109" + ] + }, + "queue_position_org": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Queue Position Org", + "description": "The position of the item in the organization's queue." + }, + "queue_position_platform": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Queue Position Platform", + "description": "The position of the item in the platform's queue." + }, + "state": { + "$ref": "#/components/schemas/ItemState", + "description": "\nThe item moves from `PENDING` to `PROCESSING` to `TERMINATED` state.\nWhen terminated, consult the `termination_reason` property to see whether it was successful.\n " + }, + "output": { + "$ref": "#/components/schemas/ItemOutput", + "description": "The output status of the item (NONE, FULL)" + }, + "termination_reason": { + "anyOf": [ + { + "$ref": "#/components/schemas/ItemTerminationReason" + }, + { + "type": "null" + } + ], + "description": "\nWhen the `state` is `TERMINATED` this will explain why\n`SUCCEEDED` -> Successful processing.\n`USER_ERROR` -> Failed because the provided input was invalid.\n`SYSTEM_ERROR` -> There was an error in the model or platform.\n`SKIPPED` -> Was cancelled\n" + }, + "error_code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Code" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message", + "description": "\n The error message in case the `termination_reason` is in `USER_ERROR` or `SYSTEM_ERROR`\n ", + "examples": [ + "This item was not processed because the threshold of 3 items finishing in error state (user or system error) was reached before the item was processed.", + "The item was not processed because the run was cancelled by the user before the item was processed.", + "User error raised by Application because the input data provided by the user cannot be processed:\nThe image width is 123000 px, but the maximum width is 100000 px", + "A system error occurred during the item execution:\n System went out of memory in cell classification", + "An unknown system error occurred during the item execution" + ] + }, + "terminated_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Terminated At", + "description": "Timestamp showing when the item reached a terminal state.", + "examples": [ + "2024-01-15T10:30:45.123Z" + ] + }, + "input_artifacts": { + "items": { + "$ref": "#/components/schemas/InputArtifactResultReadResponse" + }, + "type": "array", + "title": "Input Artifacts", + "description": "\nThe input artifact(s) provided by the user. For most applications, this will be one artifact that\ndefines the whole slide image to be processed.\n " + }, + "output_artifacts": { + "items": { + "$ref": "#/components/schemas/OutputArtifactResultReadResponse" + }, + "type": "array", + "title": "Output Artifacts", + "description": "\nThe list of the results generated by the application algorithm. The number of files and their\ntypes depend on the particular application version, call `/v1/versions/{version_id}` to get\nthe details.\n " + } + }, + "type": "object", + "required": [ + "item_id", + "external_id", + "custom_metadata", + "state", + "output", + "input_artifacts", + "output_artifacts" + ], + "title": "ItemResultReadResponse", + "description": "Response schema for items in `List Run Items` endpoint" + }, + "ItemState": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "TERMINATED" + ], + "title": "ItemState" + }, + "ItemTerminationReason": { + "type": "string", + "enum": [ + "SUCCEEDED", + "USER_ERROR", + "SYSTEM_ERROR", + "SKIPPED" + ], + "title": "ItemTerminationReason" + }, + "MeReadResponse": { + "properties": { + "user": { + "$ref": "#/components/schemas/UserReadResponse" + }, + "organization": { + "$ref": "#/components/schemas/OrganizationReadResponse" + } + }, + "type": "object", + "required": [ + "user", + "organization" + ], + "title": "MeReadResponse", + "description": "Response schema for `Get current user` endpoint" + }, + "OrganizationReadResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Unique organization identifier", + "examples": [ + "org_123456" + ] + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Organization name (E.g. “aignx”)", + "examples": [ + "aignx" + ] + }, + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Display Name", + "description": "Public organization name (E.g. “Aignostics GmbH”)", + "examples": [ + "Aignostics GmbH" + ] + }, + "aignostics_bucket_hmac_access_key_id": { + "type": "string", + "title": "Aignostics Bucket Hmac Access Key Id", + "description": "HMAC access key ID for the Aignostics-provided storage bucket. Used to authenticate requests for uploading files and generating signed URLs", + "examples": [ + "YOUR_HMAC_ACCESS_KEY_ID" + ] + }, + "aignostics_bucket_hmac_secret_access_key": { + "type": "string", + "title": "Aignostics Bucket Hmac Secret Access Key", + "description": "HMAC secret access key paired with the access key ID. Keep this credential secure.", + "examples": [ + "YOUR/HMAC/SECRET_ACCESS_KEY" + ] + }, + "aignostics_bucket_name": { + "type": "string", + "title": "Aignostics Bucket Name", + "description": "Name of the bucket provided by Aignostics for storing input artifacts (slide images)", + "examples": [ + "aignostics-platform-bucket" + ] + }, + "aignostics_bucket_protocol": { + "type": "string", + "title": "Aignostics Bucket Protocol", + "description": "Protocol to use for bucket access. Defines the URL scheme for connecting to the storage service", + "examples": [ + "gs" + ] + }, + "aignostics_logfire_token": { + "type": "string", + "title": "Aignostics Logfire Token", + "description": "Authentication token for Logfire observability service. Enables sending application logs and performance metrics to Aignostics for monitoring and support", + "examples": [ + "your-logfire-token" + ] + }, + "aignostics_sentry_dsn": { + "type": "string", + "title": "Aignostics Sentry Dsn", + "description": "Data Source Name (DSN) for Sentry error tracking service. Allows automatic reporting of errors and exceptions to Aignostics support team", + "examples": [ + "https://2354s3#ewsha@o44.ingest.us.sentry.io/34345123432" + ] + } + }, + "type": "object", + "required": [ + "id", + "aignostics_bucket_hmac_access_key_id", + "aignostics_bucket_hmac_secret_access_key", + "aignostics_bucket_name", + "aignostics_bucket_protocol", + "aignostics_logfire_token", + "aignostics_sentry_dsn" + ], + "title": "OrganizationReadResponse", + "description": "Part of response schema for Organization object in `Get current user` endpoint.\nThis model corresponds to the response schema returned from\nAuth0 GET /v2/organizations/{id} endpoint, flattens out the metadata out\nand doesn't return branding or token_quota objects.\nFor details, see:\nhttps://auth0.com/docs/api/management/v2/organizations/get-organizations-by-id\n\n#### Configuration for integrating with Aignostics Platform services.\n\nThe Aignostics Platform API requires signed URLs for input artifacts (slide images). To simplify this process,\nAignostics provides a dedicated storage bucket. The HMAC credentials below grant read and write\naccess to this bucket, allowing you to upload files and generate the signed URLs needed for API calls.\n\nAdditionally, logging and error reporting tokens enable Aignostics to provide better support and monitor\nsystem performance for your integration." + }, + "OutputArtifact": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "mime_type": { + "type": "string", + "pattern": "^\\w+\\/\\w+[-+.|\\w+]+\\w+$", + "title": "Mime Type", + "examples": [ + "application/vnd.apache.parquet" + ] + }, + "metadata_schema": { + "additionalProperties": true, + "type": "object", + "title": "Metadata Schema" + }, + "scope": { + "$ref": "#/components/schemas/OutputArtifactScope" + }, + "visibility": { + "$ref": "#/components/schemas/OutputArtifactVisibility" + } + }, + "type": "object", + "required": [ + "name", + "mime_type", + "metadata_schema", + "scope", + "visibility" + ], + "title": "OutputArtifact" + }, + "OutputArtifactResultReadResponse": { + "properties": { + "output_artifact_id": { + "type": "string", + "format": "uuid", + "title": "Output Artifact Id", + "description": "The Id of the artifact. Used internally" + }, + "name": { + "type": "string", + "title": "Name", + "description": "\nName of the output from the output schema from the `/v1/versions/{version_id}` endpoint.\n ", + "examples": [ + "tissue_qc:tiff_heatmap" + ] + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "description": "The metadata of the output artifact, provided by the application. Can only be None if the artifact itself was deleted." + }, + "state": { + "$ref": "#/components/schemas/ArtifactState", + "description": "The current state of the artifact (PENDING, PROCESSING, TERMINATED)" + }, + "termination_reason": { + "anyOf": [ + { + "$ref": "#/components/schemas/ArtifactTerminationReason" + }, + { + "type": "null" + } + ], + "description": "The reason for termination when state is TERMINATED" + }, + "output": { + "$ref": "#/components/schemas/ArtifactOutput", + "description": "The output status of the artifact (NONE, FULL)" + }, + "error_code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Code" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + }, + "download_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + { + "type": "null" + } + ], + "title": "Download Url", + "description": "\nThe download URL to the output file. The URL is valid for 1 hour after the endpoint is called.\nA new URL is generated every time the endpoint is called.\n ", + "deprecated": true + } + }, + "type": "object", + "required": [ + "output_artifact_id", + "name", + "state", + "output" + ], + "title": "OutputArtifactResultReadResponse" + }, + "OutputArtifactScope": { + "type": "string", + "enum": [ + "ITEM", + "GLOBAL" + ], + "title": "OutputArtifactScope" + }, + "OutputArtifactVisibility": { + "type": "string", + "enum": [ + "INTERNAL", + "EXTERNAL" + ], + "title": "OutputArtifactVisibility" + }, + "ResourceType": { + "type": "string", + "enum": [ + "run", + "item", + "output_artifact", + "share_token" + ], + "title": "ResourceType" + }, + "RunCreationRequest": { + "properties": { + "application_id": { + "type": "string", + "title": "Application Id", + "description": "Unique ID for the application to use for processing", + "examples": [ + "he-tme" + ] + }, + "version_number": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version Number", + "description": "Semantic version of the application to use for processing. If not provided, the latest available version will be used", + "examples": [ + "1.0.0-beta1" + ] + }, + "custom_metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata", + "description": "Optional JSON metadata to store additional information alongside the run", + "examples": [ + { + "department": "D1", + "study": "abc-1" + } + ] + }, + "scheduling": { + "anyOf": [ + { + "$ref": "#/components/schemas/SchedulingRequest" + }, + { + "type": "null" + } + ], + "description": "Optional scheduling constraints for this run.", + "examples": [ + { + "deadline": "2026-03-05T23:59:59Z", + "due_date": "2026-03-04T23:59:59Z" + } + ] + }, + "callback_context": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Callback Context", + "description": "Opaque JSON object for caller-supplied correlation data. Stored verbatim and echoed in state-change events. Max 1 KB after JSON serialization." + }, + "items": { + "items": { + "$ref": "#/components/schemas/ItemCreationRequest" + }, + "type": "array", + "minItems": 1, + "title": "Items", + "description": "List of items (slides) to process. Each item represents a whole slide image (WSI) with its associated metadata and artifacts", + "examples": [ + [ + { + "external_id": "slide_1", + "input_artifacts": [ + { + "download_url": "https://example-bucket.s3.amazonaws.com/slide1.tiff?signature=...", + "metadata": { + "checksum_base64_crc32c": "64RKKA==", + "height_px": 87761, + "media-type": "image/tiff", + "resolution_mpp": 0.2628238, + "specimen": { + "disease": "LUNG_CANCER", + "tissue": "LUNG" + }, + "staining_method": "H&E", + "width_px": 136223 + }, + "name": "input_slide" + } + ] + } + ] + ] + } + }, + "type": "object", + "required": [ + "application_id", + "items" + ], + "title": "RunCreationRequest", + "description": "Request schema for `Initiate Run` endpoint.\nIt describes which application version is chosen, and which user data should be processed." + }, + "RunCreationResponse": { + "properties": { + "run_id": { + "type": "string", + "format": "uuid", + "title": "Run Id", + "examples": [ + "3fa85f64-5717-4562-b3fc-2c963f66afa6" + ] + } + }, + "type": "object", + "required": [ + "run_id" + ], + "title": "RunCreationResponse" + }, + "RunItemStatistics": { + "properties": { + "item_count": { + "type": "integer", + "title": "Item Count", + "description": "Total number of the items in the run" + }, + "item_pending_count": { + "type": "integer", + "title": "Item Pending Count", + "description": "The number of items in `PENDING` state" + }, + "item_processing_count": { + "type": "integer", + "title": "Item Processing Count", + "description": "The number of items in `PROCESSING` state" + }, + "item_user_error_count": { + "type": "integer", + "title": "Item User Error Count", + "description": "The number of items in `TERMINATED` state, and the item termination reason is `USER_ERROR`" + }, + "item_system_error_count": { + "type": "integer", + "title": "Item System Error Count", + "description": "The number of items in `TERMINATED` state, and the item termination reason is `SYSTEM_ERROR`" + }, + "item_skipped_count": { + "type": "integer", + "title": "Item Skipped Count", + "description": "The number of items in `TERMINATED` state, and the item termination reason is `SKIPPED`" + }, + "item_succeeded_count": { + "type": "integer", + "title": "Item Succeeded Count", + "description": "The number of items in `TERMINATED` state, and the item termination reason is `SUCCEEDED`" + } + }, + "type": "object", + "required": [ + "item_count", + "item_pending_count", + "item_processing_count", + "item_user_error_count", + "item_system_error_count", + "item_skipped_count", + "item_succeeded_count" + ], + "title": "RunItemStatistics" + }, + "RunOutput": { + "type": "string", + "enum": [ + "NONE", + "PARTIAL", + "FULL" + ], + "title": "RunOutput" + }, + "RunReadResponse": { + "properties": { + "run_id": { + "type": "string", + "format": "uuid", + "title": "Run Id", + "description": "UUID of the application" + }, + "application_id": { + "type": "string", + "title": "Application Id", + "description": "Application id", + "examples": [ + "he-tme" + ] + }, + "version_number": { + "type": "string", + "title": "Version Number", + "description": "Application version number", + "examples": [ + "0.4.4" + ] + }, + "state": { + "$ref": "#/components/schemas/RunState", + "description": "When the run request is received by the Platform, the `state` of it is set to\n`PENDING`. The state changes to `PROCESSING` when at least one item is being processed. After `PROCESSING`, the\nstate of the run can switch back to `PENDING` if there are no processing items, or to `TERMINATED` when the run\nfinished processing." + }, + "output": { + "$ref": "#/components/schemas/RunOutput", + "description": "The status of the output of the run. When 0 items are successfully processed the output is\n`NONE`, after one item is successfully processed, the value is set to `PARTIAL`. When all items of the run are\nsuccessfully processed, the output is set to `FULL`." + }, + "termination_reason": { + "anyOf": [ + { + "$ref": "#/components/schemas/RunTerminationReason" + }, + { + "type": "null" + } + ], + "description": "The termination reason of the run. When the run is not in `TERMINATED` state, the\n termination_reason is `null`. If all items of of the run are processed (successfully or with an error), then\n termination_reason is set to `ALL_ITEMS_PROCESSED`. If the run is cancelled by the user, the value is set to\n `CANCELED_BY_USER`. If the run reaches the threshold of number of failed items, the Platform cancels the run\n and sets the termination_reason to `CANCELED_BY_SYSTEM`.\n " + }, + "error_code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Code", + "description": "When the termination_reason is set to CANCELED_BY_SYSTEM, the error_code is set to define the\n structured description of the error.", + "examples": [ + "SCHEDULER.ITEMS_WITH_ERROR_THRESHOLD_REACHED" + ] + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message", + "description": "When the termination_reason is set to CANCELED_BY_SYSTEM, the error_message is set to provide\n more insights to the error cause.", + "examples": [ + "Run canceled given errors on more than 10 items." + ] + }, + "statistics": { + "$ref": "#/components/schemas/RunItemStatistics", + "description": "Aggregated statistics of the run execution" + }, + "custom_metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata", + "description": "Optional JSON metadata that was stored in alongside the run by the user", + "examples": [ + { + "department": "D1", + "study": "abc-1" + } + ] + }, + "custom_metadata_checksum": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Custom Metadata Checksum", + "description": "The checksum of the `custom_metadata` field. Can be used in the `PUT /runs/{run-id}/custom_metadata`\nrequest to avoid unwanted override of the values in concurrent requests.", + "examples": [ + "f54fe109" + ] + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "title": "Submitted At", + "description": "Timestamp showing when the run was triggered" + }, + "submitted_by": { + "type": "string", + "title": "Submitted By", + "description": "Id of the user who triggered the run", + "examples": [ + "auth0|123456" + ] + }, + "organization_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Organization Id", + "description": "Uniquely identifies the organization the Run was created for" + }, + "terminated_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Terminated At", + "description": "Timestamp showing when the run reached a terminal state.", + "examples": [ + "2024-01-15T10:30:45.123Z" + ] + }, + "num_preceding_items_org": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Num Preceding Items Org", + "description": "How many Items from other Runs in the same Organization are due to begin processing before this Run's next Item does." + }, + "num_preceding_items_platform": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Num Preceding Items Platform", + "description": "How many Items from other Runs are due to begin processing before this Run's next Item does." + }, + "scheduling": { + "anyOf": [ + { + "$ref": "#/components/schemas/SchedulingResponse" + }, + { + "type": "null" + } + ], + "description": "Scheduling constraints set for this run." + } + }, + "type": "object", + "required": [ + "run_id", + "application_id", + "version_number", + "state", + "output", + "termination_reason", + "error_code", + "error_message", + "statistics", + "submitted_at", + "submitted_by" + ], + "title": "RunReadResponse", + "description": "Response schema for `Get run details` endpoint" + }, + "RunState": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "TERMINATED" + ], + "title": "RunState" + }, + "RunTerminationReason": { + "type": "string", + "enum": [ + "ALL_ITEMS_PROCESSED", + "CANCELED_BY_SYSTEM", + "CANCELED_BY_USER" + ], + "title": "RunTerminationReason" + }, + "SchedulingRequest": { + "properties": { + "due_date": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Due Date", + "description": "Requested completion time. Items are prioritized to meet this target.", + "examples": [ + "2026-03-04T23:59:59Z" + ] + }, + "deadline": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Deadline", + "description": "Hard deadline. The run will be cancelled if not completed by this time.", + "examples": [ + "2026-03-05T23:59:59Z" + ] + } + }, + "type": "object", + "title": "SchedulingRequest", + "description": "Scheduling constraints for a run." + }, + "SchedulingResponse": { + "properties": { + "due_date": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Due Date" + }, + "deadline": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Deadline" + } + }, + "type": "object", + "title": "SchedulingResponse", + "description": "Scheduling fields returned in run responses." + }, + "ShareTokenCreateRequest": { + "properties": { + "expires_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Expires At" + } + }, + "type": "object", + "title": "ShareTokenCreateRequest" + }, + "ShareTokenCreateResponse": { + "properties": { + "share_token_id": { + "type": "string", + "format": "uuid", + "title": "Share Token Id" + }, + "share_token": { + "type": "string", + "title": "Share Token" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "expires_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Expires At" + }, + "revoked": { + "type": "boolean", + "title": "Revoked" + } + }, + "type": "object", + "required": [ + "share_token_id", + "share_token", + "created_at", + "expires_at", + "revoked" + ], + "title": "ShareTokenCreateResponse", + "description": "Returned only on POST — includes the one-time share_token." + }, + "ShareTokenReadResponse": { + "properties": { + "share_token_id": { + "type": "string", + "format": "uuid", + "title": "Share Token Id" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "expires_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Expires At" + }, + "revoked": { + "type": "boolean", + "title": "Revoked" + } + }, + "type": "object", + "required": [ + "share_token_id", + "created_at", + "expires_at", + "revoked" + ], + "title": "ShareTokenReadResponse", + "description": "Returned on GET endpoints — omits share_token." + }, + "SubjectType": { + "type": "string", + "enum": [ + "user", + "organization_admin", + "organization_user", + "share_token" + ], + "title": "SubjectType" + }, + "UserReadResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Unique user identifier", + "examples": [ + "auth0|123456" + ] + }, + "email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Email", + "description": "User email", + "examples": [ + "user@domain.com" + ] + }, + "email_verified": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Email Verified", + "examples": [ + true + ] + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "First and last name of the user", + "examples": [ + "Jane Doe" + ] + }, + "given_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Given Name", + "examples": [ + "Jane" + ] + }, + "family_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Family Name", + "examples": [ + "Doe" + ] + }, + "nickname": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Nickname", + "examples": [ + "jdoe" + ] + }, + "picture": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Picture", + "examples": [ + "https://example.com/jdoe.jpg" + ] + }, + "updated_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Updated At", + "examples": [ + "2023-10-05T14:48:00.000Z" + ] + } + }, + "type": "object", + "required": [ + "id" + ], + "title": "UserReadResponse", + "description": "Part of response schema for User object in `Get current user` endpoint.\nThis model corresponds to the response schema returned from\nAuth0 GET /v2/users/{id} endpoint.\nFor details, see:\nhttps://auth0.com/docs/api/management/v2/users/get-users-by-id" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + }, + "input": { + "title": "Input" + }, + "ctx": { + "type": "object", + "title": "Context" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "VersionDocumentResponse": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "mime_type": { + "type": "string", + "title": "Mime Type" + }, + "visibility": { + "$ref": "#/components/schemas/VersionDocumentVisibility" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "name", + "mime_type", + "visibility", + "created_at", + "updated_at" + ], + "title": "VersionDocumentResponse" + }, + "VersionDocumentVisibility": { + "type": "string", + "enum": [ + "public", + "internal" + ], + "title": "VersionDocumentVisibility" + }, + "VersionReadResponse": { + "properties": { + "version_number": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "title": "Version Number", + "description": "Semantic version of the application" + }, + "changelog": { + "type": "string", + "title": "Changelog", + "description": "Description of the changes relative to the previous version" + }, + "input_artifacts": { + "items": { + "$ref": "#/components/schemas/InputArtifact" + }, + "type": "array", + "title": "Input Artifacts", + "description": "List of the input fields, provided by the User" + }, + "output_artifacts": { + "items": { + "$ref": "#/components/schemas/OutputArtifact" + }, + "type": "array", + "title": "Output Artifacts", + "description": "List of the output fields, generated by the application" + }, + "released_at": { + "type": "string", + "format": "date-time", + "title": "Released At", + "description": "The timestamp when the application version was registered" + } + }, + "type": "object", + "required": [ + "version_number", + "changelog", + "input_artifacts", + "output_artifacts", + "released_at" + ], + "title": "VersionReadResponse", + "description": "Base Response schema for the `Application Version Details` endpoint" + } + }, + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "scopes": {}, + "authorizationUrl": "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/authorize", + "tokenUrl": "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth/token" + } + } + } + } + } +} diff --git a/codegen/in/openapi.json b/codegen/in/openapi.json index 4b3e1dbee..eaa081738 100644 --- a/codegen/in/openapi.json +++ b/codegen/in/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "Aignostics Platform API", "description": "\nThe Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. \n\nTo begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. \n\nMore information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com).\n\n**How to authorize and test API endpoints:**\n\n1. Click the \"Authorize\" button in the right corner below\n3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials\n4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint\n\n**Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized.\n\n", - "version": "1.6.0" + "version": "1.6.0+dev.e1f10d7ad5b" }, "servers": [ { @@ -583,6 +583,50 @@ "title": "Page Size" } }, + { + "name": "submitted_by", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user.", + "examples": [ + "me", + "auth0|123456789" + ], + "title": "Submitted By" + }, + "description": "Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user." + }, + { + "name": "organization_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization.", + "examples": [ + "my_org", + "org_acme" + ], + "title": "Organization Id" + }, + "description": "Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization." + }, { "name": "for_organization", "in": "query", @@ -1878,6 +1922,27 @@ "title": "Subject Id" } }, + { + "name": "relation", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/GrantRelation" + } + }, + { + "type": "null" + } + ], + "description": "Filter grants by relation type. Can be specified multiple times.", + "title": "Relation" + }, + "description": "Filter grants by relation type. Can be specified multiple times." + }, { "name": "revoked", "in": "query", @@ -3726,6 +3791,18 @@ "auth0|123456" ] }, + "organization_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Organization Id", + "description": "Uniquely identifies the organization the Run was created for" + }, "terminated_at": { "anyOf": [ { @@ -4269,8 +4346,8 @@ "flows": { "authorizationCode": { "scopes": {}, - "authorizationUrl": "https://aignostics-platform-staging.eu.auth0.com/authorize", - "tokenUrl": "https://aignostics-platform-staging.eu.auth0.com/oauth/token" + "authorizationUrl": "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/authorize", + "tokenUrl": "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth/token" } } } diff --git a/codegen/out/aignx/codegen/api/public_api.py b/codegen/out/aignx/codegen/api/public_api.py index ef6ebaf79..712976630 100644 --- a/codegen/out/aignx/codegen/api/public_api.py +++ b/codegen/out/aignx/codegen/api/public_api.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. @@ -25,6 +25,7 @@ from aignx.codegen.models.custom_metadata_update_response import CustomMetadataUpdateResponse from aignx.codegen.models.grant_create_request import GrantCreateRequest from aignx.codegen.models.grant_read_response import GrantReadResponse +from aignx.codegen.models.grant_relation import GrantRelation from aignx.codegen.models.item_result_read_response import ItemResultReadResponse from aignx.codegen.models.item_state import ItemState from aignx.codegen.models.item_termination_reason import ItemTerminationReason @@ -4614,6 +4615,7 @@ def list_grants_v1_access_grants_get( resource_id: Optional[StrictStr] = None, subject_type: Optional[SubjectType] = None, subject_id: Optional[StrictStr] = None, + relation: Annotated[Optional[List[GrantRelation]], Field(description="Filter grants by relation type. Can be specified multiple times.")] = None, revoked: Optional[StrictBool] = None, page: Optional[Annotated[int, Field(strict=True, ge=1)]] = None, page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=5)]] = None, @@ -4643,6 +4645,8 @@ def list_grants_v1_access_grants_get( :type subject_type: SubjectType :param subject_id: :type subject_id: str + :param relation: Filter grants by relation type. Can be specified multiple times. + :type relation: List[GrantRelation] :param revoked: :type revoked: bool :param page: @@ -4678,6 +4682,7 @@ def list_grants_v1_access_grants_get( resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, + relation=relation, revoked=revoked, page=page, page_size=page_size, @@ -4710,6 +4715,7 @@ def list_grants_v1_access_grants_get_with_http_info( resource_id: Optional[StrictStr] = None, subject_type: Optional[SubjectType] = None, subject_id: Optional[StrictStr] = None, + relation: Annotated[Optional[List[GrantRelation]], Field(description="Filter grants by relation type. Can be specified multiple times.")] = None, revoked: Optional[StrictBool] = None, page: Optional[Annotated[int, Field(strict=True, ge=1)]] = None, page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=5)]] = None, @@ -4739,6 +4745,8 @@ def list_grants_v1_access_grants_get_with_http_info( :type subject_type: SubjectType :param subject_id: :type subject_id: str + :param relation: Filter grants by relation type. Can be specified multiple times. + :type relation: List[GrantRelation] :param revoked: :type revoked: bool :param page: @@ -4774,6 +4782,7 @@ def list_grants_v1_access_grants_get_with_http_info( resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, + relation=relation, revoked=revoked, page=page, page_size=page_size, @@ -4806,6 +4815,7 @@ def list_grants_v1_access_grants_get_without_preload_content( resource_id: Optional[StrictStr] = None, subject_type: Optional[SubjectType] = None, subject_id: Optional[StrictStr] = None, + relation: Annotated[Optional[List[GrantRelation]], Field(description="Filter grants by relation type. Can be specified multiple times.")] = None, revoked: Optional[StrictBool] = None, page: Optional[Annotated[int, Field(strict=True, ge=1)]] = None, page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=5)]] = None, @@ -4835,6 +4845,8 @@ def list_grants_v1_access_grants_get_without_preload_content( :type subject_type: SubjectType :param subject_id: :type subject_id: str + :param relation: Filter grants by relation type. Can be specified multiple times. + :type relation: List[GrantRelation] :param revoked: :type revoked: bool :param page: @@ -4870,6 +4882,7 @@ def list_grants_v1_access_grants_get_without_preload_content( resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, + relation=relation, revoked=revoked, page=page, page_size=page_size, @@ -4897,6 +4910,7 @@ def _list_grants_v1_access_grants_get_serialize( resource_id, subject_type, subject_id, + relation, revoked, page, page_size, @@ -4910,6 +4924,7 @@ def _list_grants_v1_access_grants_get_serialize( _host = None _collection_formats: Dict[str, str] = { + 'relation': 'multi', 'sort': 'multi', } @@ -4940,6 +4955,10 @@ def _list_grants_v1_access_grants_get_serialize( _query_params.append(('subject_id', subject_id)) + if relation is not None: + + _query_params.append(('relation', relation)) + if revoked is not None: _query_params.append(('revoked', revoked)) @@ -5425,6 +5444,8 @@ def list_runs_v1_runs_get( custom_metadata: Annotated[Optional[Annotated[str, Field(strict=True, max_length=1000)]], Field(description="Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata. #### URL Encoding Required **Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding. #### Examples (Clear Format): - **Field existence**: `$.study` - Runs that have a study field defined - **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value - **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75 - **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\" - **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements #### Examples (URL-Encoded Format): - **Field existence**: `%24.study` - **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)` - **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)` - **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)` - **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)` #### Notes - JSONPath expressions are evaluated using PostgreSQL's `@?` operator - The `$.` prefix is automatically added to root-level field references if missing - String values in conditions must be enclosed in double quotes - Use `&&` for AND operations and `||` for OR operations - Regular expressions use `like_regex` with standard regex syntax - **Please remember to URL-encode the entire JSONPath expression when making HTTP requests** ")] = None, page: Optional[Annotated[int, Field(strict=True, ge=1)]] = None, page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=5)]] = None, + submitted_by: Annotated[Optional[StrictStr], Field(description="Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user.")] = None, + organization_id: Annotated[Optional[StrictStr], Field(description="Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization.")] = None, for_organization: Annotated[Optional[StrictStr], Field(description="Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs.")] = None, sort: Annotated[Optional[List[StrictStr]], Field(description="Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) ")] = None, _request_timeout: Union[ @@ -5456,6 +5477,10 @@ def list_runs_v1_runs_get( :type page: int :param page_size: :type page_size: int + :param submitted_by: Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user. + :type submitted_by: str + :param organization_id: Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization. + :type organization_id: str :param for_organization: Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs. :type for_organization: str :param sort: Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) @@ -5489,6 +5514,8 @@ def list_runs_v1_runs_get( custom_metadata=custom_metadata, page=page, page_size=page_size, + submitted_by=submitted_by, + organization_id=organization_id, for_organization=for_organization, sort=sort, _request_auth=_request_auth, @@ -5522,6 +5549,8 @@ def list_runs_v1_runs_get_with_http_info( custom_metadata: Annotated[Optional[Annotated[str, Field(strict=True, max_length=1000)]], Field(description="Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata. #### URL Encoding Required **Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding. #### Examples (Clear Format): - **Field existence**: `$.study` - Runs that have a study field defined - **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value - **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75 - **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\" - **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements #### Examples (URL-Encoded Format): - **Field existence**: `%24.study` - **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)` - **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)` - **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)` - **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)` #### Notes - JSONPath expressions are evaluated using PostgreSQL's `@?` operator - The `$.` prefix is automatically added to root-level field references if missing - String values in conditions must be enclosed in double quotes - Use `&&` for AND operations and `||` for OR operations - Regular expressions use `like_regex` with standard regex syntax - **Please remember to URL-encode the entire JSONPath expression when making HTTP requests** ")] = None, page: Optional[Annotated[int, Field(strict=True, ge=1)]] = None, page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=5)]] = None, + submitted_by: Annotated[Optional[StrictStr], Field(description="Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user.")] = None, + organization_id: Annotated[Optional[StrictStr], Field(description="Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization.")] = None, for_organization: Annotated[Optional[StrictStr], Field(description="Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs.")] = None, sort: Annotated[Optional[List[StrictStr]], Field(description="Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) ")] = None, _request_timeout: Union[ @@ -5553,6 +5582,10 @@ def list_runs_v1_runs_get_with_http_info( :type page: int :param page_size: :type page_size: int + :param submitted_by: Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user. + :type submitted_by: str + :param organization_id: Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization. + :type organization_id: str :param for_organization: Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs. :type for_organization: str :param sort: Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) @@ -5586,6 +5619,8 @@ def list_runs_v1_runs_get_with_http_info( custom_metadata=custom_metadata, page=page, page_size=page_size, + submitted_by=submitted_by, + organization_id=organization_id, for_organization=for_organization, sort=sort, _request_auth=_request_auth, @@ -5619,6 +5654,8 @@ def list_runs_v1_runs_get_without_preload_content( custom_metadata: Annotated[Optional[Annotated[str, Field(strict=True, max_length=1000)]], Field(description="Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata. #### URL Encoding Required **Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding. #### Examples (Clear Format): - **Field existence**: `$.study` - Runs that have a study field defined - **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value - **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75 - **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\" - **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements #### Examples (URL-Encoded Format): - **Field existence**: `%24.study` - **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)` - **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)` - **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)` - **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)` #### Notes - JSONPath expressions are evaluated using PostgreSQL's `@?` operator - The `$.` prefix is automatically added to root-level field references if missing - String values in conditions must be enclosed in double quotes - Use `&&` for AND operations and `||` for OR operations - Regular expressions use `like_regex` with standard regex syntax - **Please remember to URL-encode the entire JSONPath expression when making HTTP requests** ")] = None, page: Optional[Annotated[int, Field(strict=True, ge=1)]] = None, page_size: Optional[Annotated[int, Field(le=100, strict=True, ge=5)]] = None, + submitted_by: Annotated[Optional[StrictStr], Field(description="Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user.")] = None, + organization_id: Annotated[Optional[StrictStr], Field(description="Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization.")] = None, for_organization: Annotated[Optional[StrictStr], Field(description="Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs.")] = None, sort: Annotated[Optional[List[StrictStr]], Field(description="Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) ")] = None, _request_timeout: Union[ @@ -5650,6 +5687,10 @@ def list_runs_v1_runs_get_without_preload_content( :type page: int :param page_size: :type page_size: int + :param submitted_by: Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user. + :type submitted_by: str + :param organization_id: Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization. + :type organization_id: str :param for_organization: Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs. :type for_organization: str :param sort: Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) @@ -5683,6 +5724,8 @@ def list_runs_v1_runs_get_without_preload_content( custom_metadata=custom_metadata, page=page, page_size=page_size, + submitted_by=submitted_by, + organization_id=organization_id, for_organization=for_organization, sort=sort, _request_auth=_request_auth, @@ -5711,6 +5754,8 @@ def _list_runs_v1_runs_get_serialize( custom_metadata, page, page_size, + submitted_by, + organization_id, for_organization, sort, _request_auth, @@ -5760,6 +5805,14 @@ def _list_runs_v1_runs_get_serialize( _query_params.append(('page_size', page_size)) + if submitted_by is not None: + + _query_params.append(('submitted_by', submitted_by)) + + if organization_id is not None: + + _query_params.append(('organization_id', organization_id)) + if for_organization is not None: _query_params.append(('for_organization', for_organization)) diff --git a/codegen/out/aignx/codegen/api_client.py b/codegen/out/aignx/codegen/api_client.py index cd8b95d27..2e1e654bc 100644 --- a/codegen/out/aignx/codegen/api_client.py +++ b/codegen/out/aignx/codegen/api_client.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/configuration.py b/codegen/out/aignx/codegen/configuration.py index 36a2ec2f8..06388ef59 100644 --- a/codegen/out/aignx/codegen/configuration.py +++ b/codegen/out/aignx/codegen/configuration.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. @@ -502,7 +502,7 @@ def to_debug_report(self) -> str: return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ - "Version of the API: 1.6.0\n"\ + "Version of the API: 1.6.0+dev.e1f10d7ad5b\n"\ "SDK Package Version: 1.0.0".\ format(env=sys.platform, pyversion=sys.version) diff --git a/codegen/out/aignx/codegen/exceptions.py b/codegen/out/aignx/codegen/exceptions.py index d5ddf1bad..614452737 100644 --- a/codegen/out/aignx/codegen/exceptions.py +++ b/codegen/out/aignx/codegen/exceptions.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/application_read_response.py b/codegen/out/aignx/codegen/models/application_read_response.py index d0836f7b7..9c5108dec 100644 --- a/codegen/out/aignx/codegen/models/application_read_response.py +++ b/codegen/out/aignx/codegen/models/application_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/application_read_short_response.py b/codegen/out/aignx/codegen/models/application_read_short_response.py index f1f01a42a..561fc9faa 100644 --- a/codegen/out/aignx/codegen/models/application_read_short_response.py +++ b/codegen/out/aignx/codegen/models/application_read_short_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/application_version.py b/codegen/out/aignx/codegen/models/application_version.py index 06096d97e..97bced6d7 100644 --- a/codegen/out/aignx/codegen/models/application_version.py +++ b/codegen/out/aignx/codegen/models/application_version.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/artifact_output.py b/codegen/out/aignx/codegen/models/artifact_output.py index 60cd2218d..fbd5cea27 100644 --- a/codegen/out/aignx/codegen/models/artifact_output.py +++ b/codegen/out/aignx/codegen/models/artifact_output.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/artifact_state.py b/codegen/out/aignx/codegen/models/artifact_state.py index d6726b17c..e57c2bd54 100644 --- a/codegen/out/aignx/codegen/models/artifact_state.py +++ b/codegen/out/aignx/codegen/models/artifact_state.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/artifact_termination_reason.py b/codegen/out/aignx/codegen/models/artifact_termination_reason.py index 564ea6639..f6e2f2e9d 100644 --- a/codegen/out/aignx/codegen/models/artifact_termination_reason.py +++ b/codegen/out/aignx/codegen/models/artifact_termination_reason.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/custom_metadata_update_request.py b/codegen/out/aignx/codegen/models/custom_metadata_update_request.py index 0645a7867..e259cb399 100644 --- a/codegen/out/aignx/codegen/models/custom_metadata_update_request.py +++ b/codegen/out/aignx/codegen/models/custom_metadata_update_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/custom_metadata_update_response.py b/codegen/out/aignx/codegen/models/custom_metadata_update_response.py index 6c1427463..c8ef533cb 100644 --- a/codegen/out/aignx/codegen/models/custom_metadata_update_response.py +++ b/codegen/out/aignx/codegen/models/custom_metadata_update_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/grant_create_request.py b/codegen/out/aignx/codegen/models/grant_create_request.py index fd437135b..1d14b9c9f 100644 --- a/codegen/out/aignx/codegen/models/grant_create_request.py +++ b/codegen/out/aignx/codegen/models/grant_create_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/grant_read_response.py b/codegen/out/aignx/codegen/models/grant_read_response.py index f60c05e99..33ec1038a 100644 --- a/codegen/out/aignx/codegen/models/grant_read_response.py +++ b/codegen/out/aignx/codegen/models/grant_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/grant_relation.py b/codegen/out/aignx/codegen/models/grant_relation.py index 58f94a71e..0ad88e189 100644 --- a/codegen/out/aignx/codegen/models/grant_relation.py +++ b/codegen/out/aignx/codegen/models/grant_relation.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/http_validation_error.py b/codegen/out/aignx/codegen/models/http_validation_error.py index 7733008d3..0577d522d 100644 --- a/codegen/out/aignx/codegen/models/http_validation_error.py +++ b/codegen/out/aignx/codegen/models/http_validation_error.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/input_artifact.py b/codegen/out/aignx/codegen/models/input_artifact.py index f97f4141e..5bcca24e0 100644 --- a/codegen/out/aignx/codegen/models/input_artifact.py +++ b/codegen/out/aignx/codegen/models/input_artifact.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/input_artifact_creation_request.py b/codegen/out/aignx/codegen/models/input_artifact_creation_request.py index 93e90992b..5f1f6a61c 100644 --- a/codegen/out/aignx/codegen/models/input_artifact_creation_request.py +++ b/codegen/out/aignx/codegen/models/input_artifact_creation_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/input_artifact_result_read_response.py b/codegen/out/aignx/codegen/models/input_artifact_result_read_response.py index e4d779dcd..e709920fd 100644 --- a/codegen/out/aignx/codegen/models/input_artifact_result_read_response.py +++ b/codegen/out/aignx/codegen/models/input_artifact_result_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/item_creation_request.py b/codegen/out/aignx/codegen/models/item_creation_request.py index c91f837f5..3ea9181ca 100644 --- a/codegen/out/aignx/codegen/models/item_creation_request.py +++ b/codegen/out/aignx/codegen/models/item_creation_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/item_output.py b/codegen/out/aignx/codegen/models/item_output.py index ad5fe184b..30693e294 100644 --- a/codegen/out/aignx/codegen/models/item_output.py +++ b/codegen/out/aignx/codegen/models/item_output.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/item_result_read_response.py b/codegen/out/aignx/codegen/models/item_result_read_response.py index 101f888cf..08f90c31e 100644 --- a/codegen/out/aignx/codegen/models/item_result_read_response.py +++ b/codegen/out/aignx/codegen/models/item_result_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/item_state.py b/codegen/out/aignx/codegen/models/item_state.py index d1ad49ecc..1d8a6fcf5 100644 --- a/codegen/out/aignx/codegen/models/item_state.py +++ b/codegen/out/aignx/codegen/models/item_state.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/item_termination_reason.py b/codegen/out/aignx/codegen/models/item_termination_reason.py index 7fd03bf45..5bc6eb46e 100644 --- a/codegen/out/aignx/codegen/models/item_termination_reason.py +++ b/codegen/out/aignx/codegen/models/item_termination_reason.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/me_read_response.py b/codegen/out/aignx/codegen/models/me_read_response.py index ca44416f8..82cc4a4fe 100644 --- a/codegen/out/aignx/codegen/models/me_read_response.py +++ b/codegen/out/aignx/codegen/models/me_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/organization_read_response.py b/codegen/out/aignx/codegen/models/organization_read_response.py index ff0aa1149..e9dbebc33 100644 --- a/codegen/out/aignx/codegen/models/organization_read_response.py +++ b/codegen/out/aignx/codegen/models/organization_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/output_artifact.py b/codegen/out/aignx/codegen/models/output_artifact.py index c0c34c9a7..425fa23f5 100644 --- a/codegen/out/aignx/codegen/models/output_artifact.py +++ b/codegen/out/aignx/codegen/models/output_artifact.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py b/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py index c111db09d..c4cc4db62 100644 --- a/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py +++ b/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/output_artifact_scope.py b/codegen/out/aignx/codegen/models/output_artifact_scope.py index 067337c08..6e594e13b 100644 --- a/codegen/out/aignx/codegen/models/output_artifact_scope.py +++ b/codegen/out/aignx/codegen/models/output_artifact_scope.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/output_artifact_visibility.py b/codegen/out/aignx/codegen/models/output_artifact_visibility.py index 2da9ccd9d..10494e80a 100644 --- a/codegen/out/aignx/codegen/models/output_artifact_visibility.py +++ b/codegen/out/aignx/codegen/models/output_artifact_visibility.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/resource_type.py b/codegen/out/aignx/codegen/models/resource_type.py index 77025b299..c2a28f534 100644 --- a/codegen/out/aignx/codegen/models/resource_type.py +++ b/codegen/out/aignx/codegen/models/resource_type.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/run_creation_request.py b/codegen/out/aignx/codegen/models/run_creation_request.py index 8cfb9be7c..1ed983c99 100644 --- a/codegen/out/aignx/codegen/models/run_creation_request.py +++ b/codegen/out/aignx/codegen/models/run_creation_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/run_creation_response.py b/codegen/out/aignx/codegen/models/run_creation_response.py index 7c3e5c0a6..5113d271e 100644 --- a/codegen/out/aignx/codegen/models/run_creation_response.py +++ b/codegen/out/aignx/codegen/models/run_creation_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/run_item_statistics.py b/codegen/out/aignx/codegen/models/run_item_statistics.py index 77128c9ff..54cb9e817 100644 --- a/codegen/out/aignx/codegen/models/run_item_statistics.py +++ b/codegen/out/aignx/codegen/models/run_item_statistics.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/run_output.py b/codegen/out/aignx/codegen/models/run_output.py index 9d925f986..ef07a4799 100644 --- a/codegen/out/aignx/codegen/models/run_output.py +++ b/codegen/out/aignx/codegen/models/run_output.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/run_read_response.py b/codegen/out/aignx/codegen/models/run_read_response.py index 46caec8fa..512c380a8 100644 --- a/codegen/out/aignx/codegen/models/run_read_response.py +++ b/codegen/out/aignx/codegen/models/run_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. @@ -45,11 +45,12 @@ class RunReadResponse(BaseModel): custom_metadata_checksum: Optional[StrictStr] = None submitted_at: datetime = Field(description="Timestamp showing when the run was triggered") submitted_by: StrictStr = Field(description="Id of the user who triggered the run") + organization_id: Optional[StrictStr] = None terminated_at: Optional[datetime] = None num_preceding_items_org: Optional[StrictInt] = None num_preceding_items_platform: Optional[StrictInt] = None scheduling: Optional[SchedulingResponse] = None - __properties: ClassVar[List[str]] = ["run_id", "application_id", "version_number", "state", "output", "termination_reason", "error_code", "error_message", "statistics", "custom_metadata", "custom_metadata_checksum", "submitted_at", "submitted_by", "terminated_at", "num_preceding_items_org", "num_preceding_items_platform", "scheduling"] + __properties: ClassVar[List[str]] = ["run_id", "application_id", "version_number", "state", "output", "termination_reason", "error_code", "error_message", "statistics", "custom_metadata", "custom_metadata_checksum", "submitted_at", "submitted_by", "organization_id", "terminated_at", "num_preceding_items_org", "num_preceding_items_platform", "scheduling"] model_config = ConfigDict( populate_by_name=True, @@ -121,6 +122,11 @@ def to_dict(self) -> Dict[str, Any]: if self.custom_metadata_checksum is None and "custom_metadata_checksum" in self.model_fields_set: _dict['custom_metadata_checksum'] = None + # set to None if organization_id (nullable) is None + # and model_fields_set contains the field + if self.organization_id is None and "organization_id" in self.model_fields_set: + _dict['organization_id'] = None + # set to None if terminated_at (nullable) is None # and model_fields_set contains the field if self.terminated_at is None and "terminated_at" in self.model_fields_set: @@ -166,6 +172,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "custom_metadata_checksum": obj.get("custom_metadata_checksum"), "submitted_at": obj.get("submitted_at"), "submitted_by": obj.get("submitted_by"), + "organization_id": obj.get("organization_id"), "terminated_at": obj.get("terminated_at"), "num_preceding_items_org": obj.get("num_preceding_items_org"), "num_preceding_items_platform": obj.get("num_preceding_items_platform"), diff --git a/codegen/out/aignx/codegen/models/run_state.py b/codegen/out/aignx/codegen/models/run_state.py index 23ed86681..c3f8a8820 100644 --- a/codegen/out/aignx/codegen/models/run_state.py +++ b/codegen/out/aignx/codegen/models/run_state.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/run_termination_reason.py b/codegen/out/aignx/codegen/models/run_termination_reason.py index a22517f6c..287129db4 100644 --- a/codegen/out/aignx/codegen/models/run_termination_reason.py +++ b/codegen/out/aignx/codegen/models/run_termination_reason.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/scheduling_request.py b/codegen/out/aignx/codegen/models/scheduling_request.py index cb5cb0007..3f8c48bdd 100644 --- a/codegen/out/aignx/codegen/models/scheduling_request.py +++ b/codegen/out/aignx/codegen/models/scheduling_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/scheduling_response.py b/codegen/out/aignx/codegen/models/scheduling_response.py index 9f305e343..341e13434 100644 --- a/codegen/out/aignx/codegen/models/scheduling_response.py +++ b/codegen/out/aignx/codegen/models/scheduling_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/share_token_create_request.py b/codegen/out/aignx/codegen/models/share_token_create_request.py index a639fcba0..3cd16149b 100644 --- a/codegen/out/aignx/codegen/models/share_token_create_request.py +++ b/codegen/out/aignx/codegen/models/share_token_create_request.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/share_token_create_response.py b/codegen/out/aignx/codegen/models/share_token_create_response.py index b0964d986..9a4f00563 100644 --- a/codegen/out/aignx/codegen/models/share_token_create_response.py +++ b/codegen/out/aignx/codegen/models/share_token_create_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/share_token_read_response.py b/codegen/out/aignx/codegen/models/share_token_read_response.py index 722bfa1bd..d84cb9546 100644 --- a/codegen/out/aignx/codegen/models/share_token_read_response.py +++ b/codegen/out/aignx/codegen/models/share_token_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/subject_type.py b/codegen/out/aignx/codegen/models/subject_type.py index e0a5c8441..b29133983 100644 --- a/codegen/out/aignx/codegen/models/subject_type.py +++ b/codegen/out/aignx/codegen/models/subject_type.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/user_read_response.py b/codegen/out/aignx/codegen/models/user_read_response.py index b1d8f1806..81d0c3a69 100644 --- a/codegen/out/aignx/codegen/models/user_read_response.py +++ b/codegen/out/aignx/codegen/models/user_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/validation_error.py b/codegen/out/aignx/codegen/models/validation_error.py index 7eef142d2..f1c84a7e5 100644 --- a/codegen/out/aignx/codegen/models/validation_error.py +++ b/codegen/out/aignx/codegen/models/validation_error.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/validation_error_loc_inner.py b/codegen/out/aignx/codegen/models/validation_error_loc_inner.py index 022f4a69a..d2dd2072f 100644 --- a/codegen/out/aignx/codegen/models/validation_error_loc_inner.py +++ b/codegen/out/aignx/codegen/models/validation_error_loc_inner.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/version_document_response.py b/codegen/out/aignx/codegen/models/version_document_response.py index f63a43711..7007e99c5 100644 --- a/codegen/out/aignx/codegen/models/version_document_response.py +++ b/codegen/out/aignx/codegen/models/version_document_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/version_document_visibility.py b/codegen/out/aignx/codegen/models/version_document_visibility.py index aeb3840c5..5fab1f449 100644 --- a/codegen/out/aignx/codegen/models/version_document_visibility.py +++ b/codegen/out/aignx/codegen/models/version_document_visibility.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/models/version_read_response.py b/codegen/out/aignx/codegen/models/version_read_response.py index 2ac813115..0d3a434de 100644 --- a/codegen/out/aignx/codegen/models/version_read_response.py +++ b/codegen/out/aignx/codegen/models/version_read_response.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/aignx/codegen/rest.py b/codegen/out/aignx/codegen/rest.py index 1f8f90e77..e55aa4613 100644 --- a/codegen/out/aignx/codegen/rest.py +++ b/codegen/out/aignx/codegen/rest.py @@ -5,7 +5,7 @@ The Aignostics Platform is a cloud-based service that enables organizations to access advanced computational pathology applications through a secure API. The platform provides standardized access to Aignostics' portfolio of computational pathology solutions, with Atlas H&E-TME serving as an example of the available API endpoints. To begin using the platform, your organization must first be registered by our business support team. If you don't have an account yet, please contact your account manager or email support@aignostics.com to get started. More information about our applications can be found on [https://platform.aignostics.com](https://platform.aignostics.com). **How to authorize and test API endpoints:** 1. Click the \"Authorize\" button in the right corner below 3. Click \"Authorize\" button in the dialog to log in with your Aignostics Platform credentials 4. After successful login, you'll be redirected back and can use \"Try it out\" on any endpoint **Note**: You only need to authorize once per session. The lock icons next to endpoints will show green when authorized. - The version of the OpenAPI document: 1.6.0 + The version of the OpenAPI document: 1.6.0+dev.e1f10d7ad5b Generated by OpenAPI Generator (https://openapi-generator.tech) Do not edit the class manually. diff --git a/codegen/out/docs/PublicApi.md b/codegen/out/docs/PublicApi.md index 6ae6863b3..a686c2d6a 100644 --- a/codegen/out/docs/PublicApi.md +++ b/codegen/out/docs/PublicApi.md @@ -1293,7 +1293,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **list_grants_v1_access_grants_get** -> List[GrantReadResponse] list_grants_v1_access_grants_get(resource_type=resource_type, resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, revoked=revoked, page=page, page_size=page_size, sort=sort) +> List[GrantReadResponse] list_grants_v1_access_grants_get(resource_type=resource_type, resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, relation=relation, revoked=revoked, page=page, page_size=page_size, sort=sort) List Grants @@ -1306,6 +1306,7 @@ List grants. Org admins see all grants for all resources in their organization. ```python import aignx.codegen from aignx.codegen.models.grant_read_response import GrantReadResponse +from aignx.codegen.models.grant_relation import GrantRelation from aignx.codegen.models.resource_type import ResourceType from aignx.codegen.models.subject_type import SubjectType from aignx.codegen.rest import ApiException @@ -1332,6 +1333,7 @@ with aignx.codegen.ApiClient(configuration) as api_client: resource_id = 'resource_id_example' # str | (optional) subject_type = aignx.codegen.SubjectType() # SubjectType | (optional) subject_id = 'subject_id_example' # str | (optional) + relation = [aignx.codegen.GrantRelation()] # List[GrantRelation] | Filter grants by relation type. Can be specified multiple times. (optional) revoked = True # bool | (optional) page = 1 # int | (optional) (default to 1) page_size = 50 # int | (optional) (default to 50) @@ -1339,7 +1341,7 @@ with aignx.codegen.ApiClient(configuration) as api_client: try: # List Grants - api_response = api_instance.list_grants_v1_access_grants_get(resource_type=resource_type, resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, revoked=revoked, page=page, page_size=page_size, sort=sort) + api_response = api_instance.list_grants_v1_access_grants_get(resource_type=resource_type, resource_id=resource_id, subject_type=subject_type, subject_id=subject_id, relation=relation, revoked=revoked, page=page, page_size=page_size, sort=sort) print("The response of PublicApi->list_grants_v1_access_grants_get:\n") pprint(api_response) except Exception as e: @@ -1357,6 +1359,7 @@ Name | Type | Description | Notes **resource_id** | **str**| | [optional] **subject_type** | [**SubjectType**](.md)| | [optional] **subject_id** | **str**| | [optional] + **relation** | [**List[GrantRelation]**](GrantRelation.md)| Filter grants by relation type. Can be specified multiple times. | [optional] **revoked** | **bool**| | [optional] **page** | **int**| | [optional] [default to 1] **page_size** | **int**| | [optional] [default to 50] @@ -1482,7 +1485,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **list_runs_v1_runs_get** -> List[RunReadResponse] list_runs_v1_runs_get(application_id=application_id, application_version=application_version, external_id=external_id, custom_metadata=custom_metadata, page=page, page_size=page_size, for_organization=for_organization, sort=sort) +> List[RunReadResponse] list_runs_v1_runs_get(application_id=application_id, application_version=application_version, external_id=external_id, custom_metadata=custom_metadata, page=page, page_size=page_size, submitted_by=submitted_by, organization_id=organization_id, for_organization=for_organization, sort=sort) List Runs @@ -1521,12 +1524,14 @@ with aignx.codegen.ApiClient(configuration) as api_client: custom_metadata = '$' # str | Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata. #### URL Encoding Required **Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding. #### Examples (Clear Format): - **Field existence**: `$.study` - Runs that have a study field defined - **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value - **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75 - **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\" - **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements #### Examples (URL-Encoded Format): - **Field existence**: `%24.study` - **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)` - **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)` - **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)` - **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)` #### Notes - JSONPath expressions are evaluated using PostgreSQL's `@?` operator - The `$.` prefix is automatically added to root-level field references if missing - String values in conditions must be enclosed in double quotes - Use `&&` for AND operations and `||` for OR operations - Regular expressions use `like_regex` with standard regex syntax - **Please remember to URL-encode the entire JSONPath expression when making HTTP requests** (optional) page = 1 # int | (optional) (default to 1) page_size = 50 # int | (optional) (default to 50) + submitted_by = 'submitted_by_example' # str | Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user. (optional) + organization_id = 'organization_id_example' # str | Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization. (optional) for_organization = 'for_organization_example' # str | Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs. (optional) sort = ['sort_example'] # List[str] | Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) (optional) try: # List Runs - api_response = api_instance.list_runs_v1_runs_get(application_id=application_id, application_version=application_version, external_id=external_id, custom_metadata=custom_metadata, page=page, page_size=page_size, for_organization=for_organization, sort=sort) + api_response = api_instance.list_runs_v1_runs_get(application_id=application_id, application_version=application_version, external_id=external_id, custom_metadata=custom_metadata, page=page, page_size=page_size, submitted_by=submitted_by, organization_id=organization_id, for_organization=for_organization, sort=sort) print("The response of PublicApi->list_runs_v1_runs_get:\n") pprint(api_response) except Exception as e: @@ -1546,6 +1551,8 @@ Name | Type | Description | Notes **custom_metadata** | **str**| Use PostgreSQL JSONPath expressions to filter runs by their custom_metadata. #### URL Encoding Required **Important**: JSONPath expressions contain special characters that must be URL-encoded when used in query parameters. Most HTTP clients handle this automatically, but when constructing URLs manually, please ensure proper encoding. #### Examples (Clear Format): - **Field existence**: `$.study` - Runs that have a study field defined - **Exact value match**: `$.study ? (@ == \"high\")` - Runs with specific study value - **Numeric comparison**: `$.confidence_score ? (@ > 0.75)` - Runs with confidence score greater than 0.75 - **Array operations**: `$.tags[*] ? (@ == \"draft\")` - Runs with tags array containing \"draft\" - **Complex conditions**: `$.resources ? (@.gpu_count > 2 && @.memory_gb >= 16)` - Runs with high resource requirements #### Examples (URL-Encoded Format): - **Field existence**: `%24.study` - **Exact value match**: `%24.study%20%3F%20(%40%20%3D%3D%20%22high%22)` - **Numeric comparison**: `%24.confidence_score%20%3F%20(%40%20%3E%200.75)` - **Array operations**: `%24.tags%5B*%5D%20%3F%20(%40%20%3D%3D%20%22draft%22)` - **Complex conditions**: `%24.resources%20%3F%20(%40.gpu_count%20%3E%202%20%26%26%20%40.memory_gb%20%3E%3D%2016)` #### Notes - JSONPath expressions are evaluated using PostgreSQL's `@?` operator - The `$.` prefix is automatically added to root-level field references if missing - String values in conditions must be enclosed in double quotes - Use `&&` for AND operations and `||` for OR operations - Regular expressions use `like_regex` with standard regex syntax - **Please remember to URL-encode the entire JSONPath expression when making HTTP requests** | [optional] **page** | **int**| | [optional] [default to 1] **page_size** | **int**| | [optional] [default to 50] + **submitted_by** | **str**| Filter runs by the user who submitted them. Use the special value `me` to return only runs submitted by the current user. | [optional] + **organization_id** | **str**| Filter runs by the organization of the submitter. Use the special value `my_org` to filter by the current user's organization. | [optional] **for_organization** | **str**| Filter runs by organization ID. Available for superadmins (any org) and admins (own org only). When provided, returns all runs for the specified organization instead of only the caller's own runs. | [optional] **sort** | [**List[str]**](str.md)| Sort the results by one or more fields. Use `+` for ascending and `-` for descending order. **Available fields:** - `run_id` - `application_id` - `version_number` - `custom_metadata` - `submitted_at` - `submitted_by` - `terminated_at` - `termination_reason` **Examples:** - `?sort=submitted_at` - Sort by creation time (ascending) - `?sort=-submitted_at` - Sort by creation time (descending) - `?sort=state&sort=-submitted_at` - Sort by state, then by time (descending) | [optional] diff --git a/pyproject.toml b/pyproject.toml index 796a1de07..bafa38c6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ dependencies = [ "procrastinate>=3.5.3", "fastparquet>=2026.3.0,<2026.4.0; python_version < '3.14'", "pyarrow>=23.0.1,<24; python_version >= '3.14'", - "pyjwt[crypto]>=2.12.0,<3", # CVE-2026-32597 requires >=2.12.0 (Renovate #475) + "pyjwt[crypto]>=2.13.0,<3", # CVE-2026-32597 requires >=2.12.0 (Renovate #475) "python-dateutil>=2.9.0.post0,<3", # "pywebview[qt6]>=5.4,<6; sys_platform == 'linux'", "requests>=2.33.0,<3", # CVE-2026-25645 requires >= 2.33.0 @@ -215,7 +215,7 @@ dev = [ "watchdog>=6.0.0,<7", # Transitive overrides # WARNING: one cannot negate or downgrade a dependency required here. use override-dependencies for that. - "pip>=26.1", # CVE-2025-8869 (Medium, >=25.3); CVE-2026-3219 (Medium, >=26.1, released 2026-04-26 via pypa/pip#13870) + "pip>=26.1.2", # CVE-2025-8869 (Medium, >=25.3); CVE-2026-3219 (Medium, >=26.1, released 2026-04-26 via pypa/pip#13870) "uv>=0.11.6", # CVE-2025-54368, GHSA-w476-p2h3-79g9, GHSA-pqhf-p39g-3x64 (>=0.9.7); GHSA-pjjw-68hj-v9mw (>=0.11.6, Renovate #536) "fonttools>=4.60.2", # CVE-2025-66034 (GHSA-768j-98cg-p3fv), dep of matplotlib "virtualenv>=20.36.1", # pypa/virtualenv#3013 TOCTOU in app_data/lock dir; bundles filelock>=3.20.1 for CVE-2025-68146; transitive via nox/pre-commit diff --git a/src/aignostics/application/_cli.py b/src/aignostics/application/_cli.py index 9ee527859..b0bf24b95 100644 --- a/src/aignostics/application/_cli.py +++ b/src/aignostics/application/_cli.py @@ -5,6 +5,7 @@ import sys import time import zipfile +from datetime import UTC, datetime from pathlib import Path from typing import Annotated @@ -130,6 +131,15 @@ result_app = typer.Typer() run_app.add_typer(result_app, name="result", help="Download or delete run results.") +share_app = typer.Typer() +run_app.add_typer(share_app, name="share", help="Manage run sharing and access.") + +share_organization_app = typer.Typer() +share_app.add_typer(share_organization_app, name="organization", help="Manage organization access grants.") + +share_token_app = typer.Typer() +share_app.add_typer(share_token_app, name="token", help="Manage share tokens for link-based access.") + version_app = typer.Typer() cli.add_typer(version_app, name="version", help="Inspect application versions and their release documents.") @@ -1303,6 +1313,209 @@ def run_update_item_metadata( sys.exit(1) +@share_app.command("status") +def run_share_status( + run_id: Annotated[str, typer.Argument(..., help="Id of the run")], + format: Annotated[str, typer.Option(help="Output format: 'text' (default) or 'json'")] = "text", # noqa: A002 +) -> None: + """Show sharing status: active organization grants and share tokens.""" + try: + org_grants = list(Service().application_run_organization_grants(run_id)) + tokens = list(Service().application_run_share_tokens(run_id)) + + if format == "json": + print( + json.dumps( + { + "organization_grants": [g.model_dump() for g in org_grants], + "share_tokens": [t.model_dump() for t in tokens], + }, + indent=2, + default=str, + ), + ) + else: + console.print(f"[bold]Organization grants[/bold] ({len(org_grants)}):") + for g in org_grants: + console.print(f" {g.grant_id} subject={g.subject_id} relation={g.relation.value}") + + console.print(f"[bold]Share tokens[/bold] ({len(tokens)}):") + for t in tokens: + expires = t.expires_at.isoformat() if t.expires_at else "never" + created = t.created_at.isoformat() + console.print(f" {t.share_token_id} created={created} expires={expires}") + except NotFoundException: + console.print(f"[warning]Warning:[/warning] Run with ID '{run_id}' not found.") + sys.exit(2) + except Exception as e: + logger.exception("Failed to retrieve share status for run '{}'", run_id) + console.print(f"[error]Error:[/error] Failed to retrieve share status for run '{run_id}': {e}") + sys.exit(1) + + +@share_organization_app.command("list") +def run_share_organization_list( + run_id: Annotated[str, typer.Argument(..., help="Id of the run")], + format: Annotated[str, typer.Option(help="Output format: 'text' (default) or 'json'")] = "text", # noqa: A002 +) -> None: + """List active organization grants for a run.""" + try: + grants = list(Service().application_run_organization_grants(run_id)) + if format == "json": + print(json.dumps([g.model_dump() for g in grants], indent=2, default=str)) + else: + if not grants: + console.print("No active organization grants.") + for g in grants: + console.print( + f"{g.grant_id} subject={g.subject_id}" + f" relation={g.relation.value} created={g.created_at.isoformat()}", + ) + except NotFoundException: + console.print(f"[warning]Warning:[/warning] Run with ID '{run_id}' not found.") + sys.exit(2) + except Exception as e: + logger.exception("Failed to list organization grants for run '{}'", run_id) + console.print(f"[error]Error:[/error] Failed to list organization grants for run '{run_id}': {e}") + sys.exit(1) + + +@share_organization_app.command("grant") +def run_share_organization_grant( + run_id: Annotated[str, typer.Argument(..., help="Id of the run to share")], + organization_id: Annotated[ + str | None, + typer.Argument(help="Organization ID to share with (defaults to your own organization)"), + ] = None, + format: Annotated[str, typer.Option(help="Output format: 'text' (default) or 'json'")] = "text", # noqa: A002 +) -> None: + """Share a run with all users in an organization.""" + try: + grant = Service().application_run_share_with_organization(run_id, organization_id=organization_id) + if format == "json": + print(json.dumps(grant.model_dump(), indent=2, default=str)) + else: + console.print(f"Run '{run_id}' is now shared with organization (grant {grant.grant_id}).") + except NotFoundException: + console.print(f"[warning]Warning:[/warning] Run with ID '{run_id}' not found.") + sys.exit(2) + except Exception as e: + logger.exception("Failed to share run '{}' with organization", run_id) + console.print(f"[error]Error:[/error] Failed to share run '{run_id}' with organization: {e}") + sys.exit(1) + + +@share_organization_app.command("revoke") +def run_share_organization_revoke( + run_id: Annotated[str, typer.Argument(..., help="Id of the run to unshare")], + organization_id: Annotated[ + str | None, + typer.Argument(help="Organization ID to revoke access for (defaults to your own organization)"), + ] = None, +) -> None: + """Revoke organization grants for a run.""" + try: + Service().application_run_unshare_with_organization(run_id, organization_id=organization_id) + console.print(f"Organization access revoked for run '{run_id}'.") + except NotFoundException: + console.print(f"[warning]Warning:[/warning] Run with ID '{run_id}' not found.") + sys.exit(2) + except Exception as e: + logger.exception("Failed to revoke organization access for run '{}'", run_id) + console.print(f"[error]Error:[/error] Failed to revoke organization access for run '{run_id}': {e}") + sys.exit(1) + + +@share_token_app.command("list") +def run_share_token_list( + run_id: Annotated[str, typer.Argument(..., help="Id of the run")], + format: Annotated[str, typer.Option(help="Output format: 'text' (default) or 'json'")] = "text", # noqa: A002 +) -> None: + """List active share tokens for a run.""" + try: + tokens = list(Service().application_run_share_tokens(run_id)) + + if format == "json": + print(json.dumps([t.model_dump() for t in tokens], indent=2, default=str)) + else: + if not tokens: + console.print("No active share tokens.") + for t in tokens: + expires = t.expires_at.isoformat() if t.expires_at else "never" + created = t.created_at.isoformat() if t.created_at else "unknown" + console.print(f"{t.share_token_id} created={created} expires={expires}") + except NotFoundException: + console.print(f"[warning]Warning:[/warning] Run with ID '{run_id}' not found.") + sys.exit(2) + except Exception as e: + logger.exception("Failed to list share tokens for run '{}'", run_id) + console.print(f"[error]Error:[/error] Failed to list share tokens for run '{run_id}': {e}") + sys.exit(1) + + +@share_token_app.command("create") +def run_share_token_create( + run_id: Annotated[str, typer.Argument(..., help="Id of the run to create a share token for")], + expires_at: Annotated[ + str | None, + typer.Option( + help="Expiry datetime in ISO 8601 format, e.g. '2026-12-31T23:59:59Z'. Omit for a non-expiring token.", + ), + ] = None, + format: Annotated[str, typer.Option(help="Output format: 'text' (default) or 'json'")] = "text", # noqa: A002 +) -> None: + """Create a share token for a run. The token value is shown only once.""" + expires_at_dt: datetime | None = None + if expires_at is not None: + try: + expires_at_normalized = expires_at.replace("Z", "+00:00") if expires_at.endswith("Z") else expires_at + expires_at_dt = datetime.fromisoformat(expires_at_normalized) + if expires_at_dt.tzinfo is None: + expires_at_dt = expires_at_dt.replace(tzinfo=UTC) + except ValueError: + console.print( + f"[error]Error:[/error] Invalid --expires-at value '{expires_at}'. " + "Use ISO 8601 format, e.g. '2026-12-31T23:59:59Z'." + ) + sys.exit(1) + try: + token = Service().application_run_create_share_token(run_id, expires_at=expires_at_dt) + if format == "json": + print(json.dumps(token.model_dump(), indent=2, default=str)) + else: + expires = token.expires_at.isoformat() if token.expires_at else "never" + console.print(f"Share token created for run '{run_id}'.") + console.print(f" Token ID : {token.share_token_id}") + console.print(f" Token : [bold]{token.share_token}[/bold]") + console.print(f" Expires : {expires}") + console.print("[yellow]Save the token value — it will not be shown again.[/yellow]") + except NotFoundException: + console.print(f"[warning]Warning:[/warning] Run with ID '{run_id}' not found.") + sys.exit(2) + except Exception as e: + logger.exception("Failed to create share token for run '{}'", run_id) + console.print(f"[error]Error:[/error] Failed to create share token for run '{run_id}': {e}") + sys.exit(1) + + +@share_token_app.command("revoke") +def run_share_token_revoke( + run_id: Annotated[str, typer.Argument(..., help="Id of the run")], + token_id: Annotated[str, typer.Argument(..., help="Id of the share token to revoke")], +) -> None: + """Revoke a share token.""" + try: + Service().application_run_revoke_share_token(run_id, token_id) + console.print(f"Share token '{token_id}' revoked for run '{run_id}'.") + except NotFoundException as e: + console.print(f"[warning]Warning:[/warning] {e}") + sys.exit(2) + except Exception as e: + logger.exception("Failed to revoke share token '{}' for run '{}'", token_id, run_id) + console.print(f"[error]Error:[/error] Failed to revoke share token '{token_id}' for run '{run_id}': {e}") + sys.exit(1) + + @result_app.command("download") def result_download( # noqa: C901, PLR0913, PLR0915, PLR0917 run_id: Annotated[str, typer.Argument(..., help="Id of the run to download results for")], diff --git a/src/aignostics/application/_service.py b/src/aignostics/application/_service.py index 315c674f6..a47b27abd 100644 --- a/src/aignostics/application/_service.py +++ b/src/aignostics/application/_service.py @@ -3,7 +3,7 @@ import base64 import re import time -from collections.abc import Callable, Generator +from collections.abc import Callable, Generator, Iterator from datetime import datetime from http import HTTPStatus from importlib.util import find_spec @@ -12,6 +12,7 @@ import crc32c import requests +from aignx.codegen.models import GrantRelation, SubjectType from loguru import logger from aignostics.bucket import Service as BucketService @@ -33,6 +34,7 @@ RunState, ) from aignostics.platform import Service as PlatformService +from aignostics.platform.resources.access import AccessGrant, ShareToken from aignostics.utils import BaseService, Health, sanitize_path_component from aignostics.wsi import Service as WSIService @@ -1323,6 +1325,197 @@ def application_run_delete(self, run_id: str) -> None: logger.exception(message) raise RuntimeError(message) from e + def application_run_organization_grants( + self, + run_id: str, + page_size: int = LIST_APPLICATION_RUNS_MAX_PAGE_SIZE, + ) -> Iterator[AccessGrant]: + """List active organization grants for a run. + + Args: + run_id (str): The ID of the run. + page_size (int): Number of grants per page. Defaults to max (100). + + Returns: + Iterator[AccessGrant]: Active grants for this run. + + Raises: + NotFoundException: If the run is not found. + RuntimeError: If the request fails unexpectedly. + """ + try: + return self.application_run(run_id).list_share_grants( + subject_type=SubjectType.ORGANIZATION_USER, + relation=[GrantRelation.VIEWER], + page_size=page_size, + ) + except NotFoundException as e: + message = f"Application run with ID '{run_id}' not found: {e}" + logger.warning(message) + raise NotFoundException(message) from e + except Exception as e: + message = f"Failed to list organization grants for run '{run_id}': {e}" + logger.exception(message) + raise RuntimeError(message) from e + + def application_run_share_tokens( + self, + run_id: str, + page_size: int = LIST_APPLICATION_RUNS_MAX_PAGE_SIZE, + ) -> Iterator[ShareToken]: + """List active share tokens for a run. + + Args: + run_id (str): The ID of the run. + page_size (int): Number of tokens per page. Defaults to max (100). + + Returns: + Iterator[ShareToken]: Active share tokens. + + Raises: + NotFoundException: If the run is not found. + RuntimeError: If the request fails unexpectedly. + """ + try: + return self._get_platform_client().share_tokens.list(run_id=run_id, page_size=page_size) + except NotFoundException as e: + message = f"Application run with ID '{run_id}' not found: {e}" + logger.warning(message) + raise NotFoundException(message) from e + except Exception as e: + message = f"Failed to list share tokens for run '{run_id}': {e}" + logger.exception(message) + raise RuntimeError(message) from e + + def application_run_share_with_organization( + self, + run_id: str, + organization_id: str | None = None, + ) -> AccessGrant: + """Share a run with all users in an organization. + + Args: + run_id (str): The ID of the run. + organization_id (str | None): The organization to share with. Defaults to + the authenticated user's own organization. + + Returns: + AccessGrant: The created grant. + + Raises: + NotFoundException: If the run is not found. + RuntimeError: If the request fails unexpectedly. + """ + try: + organization_id = organization_id or self._get_platform_client().me().organization.id + return self.application_run(run_id).grant_access( + subject_type=SubjectType.ORGANIZATION_USER, + subject_id=organization_id, + ) + except NotFoundException as e: + message = f"Application run with ID '{run_id}' not found: {e}" + logger.warning(message) + raise NotFoundException(message) from e + except Exception as e: + message = f"Failed to share run '{run_id}' with organization {organization_id}: {e}" + logger.exception(message) + raise RuntimeError(message) from e + + def application_run_unshare_with_organization(self, run_id: str, organization_id: str | None = None) -> None: + """Revoke active organization grants for a run. + + Args: + run_id (str): The ID of the run. + organization_id (str | None): Organization whose grants to revoke. + Defaults to the authenticated user's own organization. + + Raises: + NotFoundException: If the run is not found. + RuntimeError: If the request fails unexpectedly. + """ + try: + organization_id = organization_id or self._get_platform_client().me().organization.id + for grant in self.application_run(run_id).list_share_grants( + subject_type=SubjectType.ORGANIZATION_USER, + subject_id=organization_id, + relation=[GrantRelation.VIEWER], + ): + grant.revoke() + except NotFoundException as e: + message = f"Application run with ID '{run_id}' not found: {e}" + logger.warning(message) + raise NotFoundException(message) from e + except Exception as e: + message = f"Failed to unshare run '{run_id}' with organization: {e}" + logger.exception(message) + raise RuntimeError(message) from e + + def application_run_create_share_token(self, run_id: str, expires_at: datetime | None = None) -> ShareToken: + """Create a share token for a run. + + Args: + run_id (str): The ID of the run. + expires_at (datetime | None): Optional UTC datetime at which the token expires. + Pass ``None`` (default) for a token that never expires. + + Returns: + ShareToken: The created token. Access the one-time secret via ``share_token``. + + Raises: + NotFoundException: If the run is not found. + RuntimeError: If the request fails unexpectedly. + """ + try: + share_token = self._get_platform_client().share_tokens.create(expires_at=expires_at) + self.application_run(run_id).grant_access( + subject_type=SubjectType.SHARE_TOKEN, + subject_id=share_token.share_token_id, + ) + return share_token + except NotFoundException as e: + message = f"Application run with ID '{run_id}' not found: {e}" + logger.warning(message) + raise NotFoundException(message) from e + except Exception as e: + message = f"Failed to create share token for run '{run_id}': {e}" + logger.exception(message) + raise RuntimeError(message) from e + + def application_run_revoke_share_token(self, run_id: str, share_token_id: str) -> None: + """Revoke the grant giving a share token access to a run. + + Removes the token's access to this specific run without invalidating + the token itself; the token may still be valid for other runs. + + Args: + run_id (str): The ID of the run. + share_token_id (str): The ID of the share token whose grant to revoke. + + Raises: + NotFoundException: If the run is not found or no grant exists for + the token on this run. + RuntimeError: If the request fails unexpectedly. + """ + try: + grants = list( + self.application_run(run_id).list_share_grants( + subject_type=SubjectType.SHARE_TOKEN, + subject_id=share_token_id, + ) + ) + for grant in grants: + grant.revoke() + except NotFoundException: + raise + except Exception as e: + message = f"Failed to revoke share token '{share_token_id}' for run '{run_id}': {e}" + logger.exception(message) + raise RuntimeError(message) from e + if not grants: + message = f"No grant found for share token '{share_token_id}' on run '{run_id}'" + logger.warning(message) + raise NotFoundException(message) + @staticmethod def application_run_download_static( # noqa: PLR0913, PLR0917 run_id: str, diff --git a/src/aignostics/platform/_client.py b/src/aignostics/platform/_client.py index 5968c0e79..bb054731b 100644 --- a/src/aignostics/platform/_client.py +++ b/src/aignostics/platform/_client.py @@ -30,6 +30,7 @@ from aignostics.utils import user_agent from ._settings import settings +from .resources.access import ShareTokens # Safety bound for the external token-provider cache. In normal usage callers # reuse a single provider reference, so this limit should never be reached. @@ -54,6 +55,7 @@ class Client: applications: Applications versions: Versions runs: Runs + share_tokens: ShareTokens def __init__(self, cache_token: bool = True, token_provider: Callable[[], str] | None = None) -> None: """Initializes a client instance with authenticated API access. @@ -78,6 +80,7 @@ def __init__(self, cache_token: bool = True, token_provider: Callable[[], str] | self._api = Client.get_api_client(cache_token=cache_token, token_provider=token_provider) self.applications: Applications = Applications(self._api) self.runs: Runs = Runs(self._api) + self.share_tokens: ShareTokens = ShareTokens(self._api) self.versions: Versions = Versions(self._api) logger.trace("Client initialized successfully.") except Exception: diff --git a/src/aignostics/platform/resources/access.py b/src/aignostics/platform/resources/access.py new file mode 100644 index 000000000..bd43715be --- /dev/null +++ b/src/aignostics/platform/resources/access.py @@ -0,0 +1,446 @@ +"""Access-control resources: organization grants and share tokens. + +This module provides classes for managing access to Aignostics platform resources. +There are two complementary mechanisms: + +* **Share grants** (``AccessGrant``) — delegate access to an existing platform + user or organization directly. Grants are always associated with a specific + resource (e.g. a run) and a subject (e.g. an organization). + +* **Share tokens** (``ShareToken``) — create a short-lived, revocable secret that + can be handed to anyone. The recipient exchanges the token for a grant without + needing a platform account. + +Typical workflow:: + + from aignostics.platform import Client + from aignx.codegen.models import SubjectType + + client = Client() + + # --- Share a run with another organization via a grant --- + run = client.run("run-abc123") + grant = run.grant_access( + subject_type=SubjectType.ORGANIZATION_USER, + subject_id="org-xyz", + ) + print(f"Granted access: {grant.grant_id}") + + # List all active grants on the run + for g in run.list_share_grants(): + print(g.grant_id, g.subject_type, g.subject_id) + + # Revoke a specific grant + grant.revoke() + + # --- Share a run via a one-time token --- + token = client.share_tokens.create() + print(f"Share this token secret once: {token.share_token}") + + # Grant the token access to the run + run.grant_access( + subject_type=SubjectType.SHARE_TOKEN, + subject_id=token.share_token_id, + ) + + # List tokens and revoke one + for t in client.share_tokens.list(): + print(t.share_token_id, t.expires_at) + token.revoke() +""" + +import builtins +from collections.abc import Iterator +from datetime import datetime +from typing import Any, cast + +from aignx.codegen.models import ( + GrantReadResponse, + GrantRelation, + ResourceType, + ShareTokenCreateRequest, + SubjectType, +) +from pydantic import BaseModel, ConfigDict, PrivateAttr +from tenacity import Retrying, retry_if_exception_type, stop_after_attempt, wait_exponential_jitter + +from aignostics.platform._api import RETRYABLE_EXCEPTIONS, _AuthenticatedApi, _AuthenticatedResource, _log_retry_attempt +from aignostics.platform._operation_cache import cached_operation, operation_cache_clear +from aignostics.platform._settings import settings +from aignostics.platform.resources.utils import paginate +from aignostics.utils import user_agent + + +class AccessGrant(BaseModel): + """An active access grant linking a platform resource to a subject. + + A grant gives a *subject* (an organization, an organization user, or a + share token) a specific *relation* (e.g. ``VIEWER``) on a *resource*. + + Instances are returned by resource-level helpers such as + ``resource.grant_access()`` and ``resource.list_share_grants()``, or + fetched directly via ``AccessGrant.for_grant_id()``. + + Attributes: + grant_id: Unique identifier for this grant. + resource_type: Type of the resource this grant applies to (e.g. ``RUN``). + resource_id: Identifier of the resource (e.g. the run ID). + subject_id: Identifier of the entity that was granted access. + subject_type: Category of the subject (``ORGANIZATION_ADMIN``, + ``ORGANIZATION_USER``, or ``SHARE_TOKEN``). + relation: Level of access granted (currently always ``VIEWER``). + created_by: ID of the user who created this grant. + created_at: UTC timestamp when the grant was created. + revoked: ``True`` if the grant has already been revoked. + + Example:: + + grant = AccessGrant.for_grant_id("grant-abc123") + print(grant.subject_type, grant.relation, grant.revoked) + + # Remove the grant + grant.revoke() + """ + + model_config = ConfigDict(arbitrary_types_allowed=True) + + _api: _AuthenticatedApi = PrivateAttr() + grant_id: str + resource_type: ResourceType + resource_id: str + subject_id: str + subject_type: SubjectType + relation: GrantRelation + created_by: str + created_at: datetime + revoked: bool + + def __init__(self, *, api: _AuthenticatedApi, **data: Any) -> None: # noqa: ANN401, D107 + super().__init__(**data) + self._api = api + + def revoke(self) -> None: + """Revoke this grant, removing the subject's access to the resource. + + After this call the in-memory ``revoked`` attribute is *not* updated; + call ``AccessGrant.for_grant_id(self.grant_id)`` if you need a fresh + server-side view. + + Raises: + Exception: If the API request fails. + """ + self._api.revoke_grant_v1_access_grants_grant_id_delete( + grant_id=self.grant_id, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + ) + operation_cache_clear() + + @classmethod + def for_grant_id(cls, grant_id: str, cache_token: bool = True) -> "AccessGrant": + """Retrieve a single grant by its ID. + + Args: + grant_id: The unique identifier of the grant to fetch. + cache_token: Whether to use the cached authentication token. + Defaults to ``True``. + + Returns: + The ``AccessGrant`` corresponding to *grant_id*. + + Raises: + NotFoundException: If no grant with the given ID exists. + Exception: If the API request fails. + + Example:: + + grant = AccessGrant.for_grant_id("grant-abc123") + print(grant.subject_type, grant.revoked) + """ + from aignostics.platform._client import Client # noqa: PLC0415 + + api = Client.get_api_client(cache_token=cache_token) + + grant = api.get_grant_v1_access_grants_grant_id_get( + grant_id=grant_id, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + ) + + return cls(api=api, **grant.__dict__) + + +class ShareToken(BaseModel): + """A share token that can be used to grant access to platform resources. + + Share tokens decouple *token creation* from *grant creation*: a token is + minted first, then attached to one or more resources as a subject of type + ``SHARE_TOKEN``. The secret value (``share_token``) is only available + immediately after creation — it is never stored by the platform and will be + ``None`` for tokens fetched later via ``ShareToken.for_token_id()``. + + Attributes: + share_token_id: Stable identifier for this token (safe to persist). + revoked: ``True`` if the token has been revoked. + created_at: UTC timestamp when the token was created. + expires_at: Optional UTC expiry; ``None`` means the token never expires. + share_token: One-time secret value. Only present immediately after + ``ShareTokens.create()``; ``None`` for subsequently fetched tokens. + + Example:: + + from aignostics.platform import Client + + client = Client() + + # Create a token and note the secret — it won't be retrievable later + token = client.share_tokens.create() + secret = token.share_token # store or transmit this once + token_id = token.share_token_id # stable ID for revocation + + # Fetch the token record later (secret is gone) + fetched = ShareToken.for_token_id(token_id) + assert fetched.share_token is None + + # List grants created for this token + for grant in fetched.list_share_grants(): + print(grant.grant_id, grant.relation) + + # Revoke the token (all associated grants become ineffective) + fetched.revoke() + """ + + model_config = ConfigDict(arbitrary_types_allowed=True) + + _api: _AuthenticatedApi = PrivateAttr() + + share_token_id: str + revoked: bool + created_at: datetime + expires_at: datetime | None = None + share_token: str | None = None + + def __init__(self, *, api: _AuthenticatedApi, **data: Any) -> None: # noqa: ANN401, D107 + super().__init__(**data) + self._api = api + + @classmethod + def for_token_id(cls, share_token_id: str, cache_token: bool = True) -> "ShareToken": + """Retrieve a share token record by its stable ID. + + The returned object will have ``share_token = None`` because the secret + is only returned at creation time. + + Args: + share_token_id: The stable ID of the token to fetch. + cache_token: Whether to use the cached authentication token. + Defaults to ``True``. + + Returns: + The ``ShareToken`` corresponding to *share_token_id*. + + Raises: + NotFoundException: If no token with the given ID exists. + Exception: If the API request fails. + + Example:: + + token = ShareToken.for_token_id("tok-abc123") + print(token.revoked, token.expires_at) + """ + from aignostics.platform._client import Client # noqa: PLC0415 + + api = Client.get_api_client(cache_token=cache_token) + token = api.get_share_token_v1_access_share_tokens_share_token_id_get( + share_token_id=share_token_id, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + ) + + return ShareToken(api=api, **token.__dict__) + + def list_share_grants(self, *, page_size: int = 100) -> Iterator[AccessGrant]: + """List all active grants where this token is the subject. + + Each returned ``AccessGrant`` represents a resource this token has been + granted access to. Call ``grant.revoke()`` to remove access to a + specific resource without invalidating the token itself. + + Args: + page_size: Number of grants to fetch per page (max 100). + + Returns: + Iterator of ``AccessGrant`` objects for this token. + + Raises: + Exception: If the API request fails. + + Example:: + + token = client.share_tokens.create() + for grant in token.list_share_grants(): + print(grant.grant_id, grant.relation) + """ + + def fetch_page(**kwargs: object) -> list[GrantReadResponse]: + return cast( + "list[GrantReadResponse]", + self._api.list_grants_v1_access_grants_get( + subject_type=SubjectType.SHARE_TOKEN, + subject_id=self.share_token_id, + revoked=False, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + **kwargs, # pyright: ignore[reportArgumentType] + ), + ) + + return (AccessGrant(api=self._api, **g.__dict__) for g in paginate(fetch_page, page_size=page_size)) + + def revoke(self) -> None: + """Revoke this share token, invalidating all grants associated with it. + + After revocation any resource that was shared via this token becomes + inaccessible to its holder. The in-memory ``revoked`` attribute is + *not* updated in-place; fetch a fresh record via + ``ShareToken.for_token_id()`` if you need the server-side state. + + Raises: + Exception: If the API request fails. + """ + self._api.revoke_share_token_v1_access_share_tokens_share_token_id_delete( + share_token_id=self.share_token_id, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + ) + operation_cache_clear() + + +class ShareTokens(_AuthenticatedResource): + """Collection resource for managing share tokens. + + Accessible as ``client.share_tokens``. Use ``create()`` to mint a new + token and ``list()`` to enumerate existing ones. + + Example:: + + from aignostics.platform import Client + from datetime import datetime, timedelta, timezone + + client = Client() + + # Create a token that expires in 7 days + token = client.share_tokens.create( + expires_at=datetime.now(timezone.utc) + timedelta(days=7), + ) + print("Secret (store once):", token.share_token) + print("Token ID:", token.share_token_id) + + # List all active tokens + for t in client.share_tokens.list(): + print(t.share_token_id, t.expires_at, t.revoked) + """ + + def __init__(self, api: _AuthenticatedApi) -> None: # noqa: D107 + super().__init__(api) + + def list(self, *, run_id: str | None = None, nocache: bool = False, page_size: int = 100) -> Iterator[ShareToken]: + """List all share tokens for the authenticated user. + + Results are cached for ``run_cache_ttl`` seconds and retried on + transient network or server errors. + + Args: + run_id: Optional run ID to filter tokens by the run they are associated with. + Defaults to ``None`` (no filter). + nocache: If ``True``, bypass the local cache and fetch fresh data + from the API. The fetched result is still written to the cache. + Defaults to ``False``. + page_size: Number of tokens to fetch per page (max 100). + Defaults to 100. + + Returns: + Iterator of ``ShareToken`` objects. + + Raises: + Exception: If the API request fails after all retries. + + Example:: + + for token in client.share_tokens.list(): + print(token.share_token_id, token.revoked) + + # Force a fresh fetch after creating a new token + for token in client.share_tokens.list(nocache=True): + print(token.share_token_id) + """ + + @cached_operation(ttl=settings().run_cache_ttl, token_provider=self._api.token_provider) + def list_data_with_retry(cached_run_id: str | None, **kwargs: object) -> builtins.list[ShareToken]: + return Retrying( + retry=retry_if_exception_type(exception_types=RETRYABLE_EXCEPTIONS), + stop=stop_after_attempt(settings().run_retry_attempts), + wait=wait_exponential_jitter(initial=settings().run_retry_wait_min, max=settings().run_retry_wait_max), + before_sleep=_log_retry_attempt, + reraise=True, + )( + lambda: [ + ShareToken(api=self._api, **t.__dict__) + for t in self._api.list_share_tokens_v1_access_share_tokens_get( + run_id=cached_run_id, + revoked=False, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + **kwargs, # pyright: ignore[reportArgumentType] + ) + ] + ) + + return paginate( + lambda **kwargs: list_data_with_retry( + run_id, + nocache=nocache, + **kwargs, + ), + page_size=page_size, + ) + + def create( + self, + expires_at: datetime | None = None, + ) -> ShareToken: + """Create a new share token. + + The returned ``ShareToken`` contains the one-time secret in + ``share_token``. This is the **only** time the secret is returned by + the API — subsequent fetches via ``ShareToken.for_token_id()`` will + have ``share_token = None``. + + Args: + expires_at: Optional UTC datetime at which the token expires. + Pass ``None`` (default) for a token that never expires. + + Returns: + A newly created ``ShareToken`` with ``share_token`` populated. + + Raises: + Exception: If the API request fails. + + Example:: + + from datetime import datetime, timedelta, timezone + + # Token valid for 24 hours + token = client.share_tokens.create( + expires_at=datetime.now(timezone.utc) + timedelta(hours=24), + ) + secret = token.share_token # transmit to the intended recipient + """ + share_token = self._api.create_share_token_v1_access_share_tokens_post( + share_token_create_request=ShareTokenCreateRequest(expires_at=expires_at), + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + ) + operation_cache_clear() + + return ShareToken(api=self._api, **share_token.__dict__) diff --git a/src/aignostics/platform/resources/runs.py b/src/aignostics/platform/resources/runs.py index 508a547bc..a2097db64 100644 --- a/src/aignostics/platform/resources/runs.py +++ b/src/aignostics/platform/resources/runs.py @@ -18,15 +18,20 @@ from aignx.codegen.models import ( ArtifactOutput, CustomMetadataUpdateRequest, + GrantCreateRequest, + GrantReadResponse, + GrantRelation, ItemCreationRequest, ItemOutput, ItemResultReadResponse, ItemState, ItemTerminationReason, + ResourceType, RunCreationRequest, RunCreationResponse, RunState, SchedulingRequest, + SubjectType, ) from aignx.codegen.models import ( ItemResultReadResponse as ItemResultData, @@ -71,6 +76,7 @@ get_mime_type_for_artifact, mime_type_to_file_ending, ) +from aignostics.platform.resources.access import AccessGrant from aignostics.platform.resources.applications import Versions from aignostics.platform.resources.utils import paginate from aignostics.utils import user_agent @@ -654,6 +660,112 @@ def update_item_custom_metadata( ) operation_cache_clear() # Clear all caches since we updated a run + def list_share_grants( + self, + subject_type: SubjectType | None = None, + subject_id: str | None = None, + relation: list[GrantRelation] | None = None, + page_size: int = LIST_APPLICATION_RUNS_MAX_PAGE_SIZE, + nocache: bool = False, + ) -> Iterator[AccessGrant]: + """List active access grants for this run. + + Supports optional filtering by subject type, subject ID, and relation. + + Args: + subject_type: Filter by subject type (e.g. ``ORGANIZATION_USER``, ``SHARE_TOKEN``). + Defaults to ``None`` (no filter). + subject_id: Filter by subject ID. Defaults to ``None``. + relation: Filter by relation type(s). Defaults to ``None``. + page_size: Number of grants per page. Defaults to max (100). + nocache: If ``True``, bypass cache and fetch fresh data. Defaults to ``False``. + + Returns: + Iterator[AccessGrant]: Active grants for this run. + + Raises: + ValueError: If page_size is greater than 100. + Exception: If the API request fails. + """ + if page_size > LIST_APPLICATION_RUNS_MAX_PAGE_SIZE: + message = f"page_size must be <= {LIST_APPLICATION_RUNS_MAX_PAGE_SIZE}, but got {page_size}" + raise ValueError(message) + + run_id = self.run_id # capture explicitly so it enters the cache key as an arg + + @cached_operation(ttl=settings().run_cache_ttl, token_provider=self._api.token_provider) + def fetch_grant_page(cached_run_id: str, **kwargs: object) -> list[GrantReadResponse]: + return Retrying( + retry=retry_if_exception_type(exception_types=RETRYABLE_EXCEPTIONS), + stop=stop_after_attempt(settings().run_retry_attempts), + wait=wait_exponential_jitter(initial=settings().run_retry_wait_min, max=settings().run_retry_wait_max), + before_sleep=_log_retry_attempt, + reraise=True, + )( + lambda: self._api.list_grants_v1_access_grants_get( + resource_type=ResourceType.RUN, + resource_id=cached_run_id, + revoked=False, + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + **kwargs, # pyright: ignore[reportArgumentType] + ) + ) + + return ( + AccessGrant( + api=self._api, + **g.__dict__, + ) + for g in paginate( + lambda **kw: fetch_grant_page( + run_id, + nocache=nocache, + subject_type=subject_type, + subject_id=subject_id, + relation=relation, + **kw, + ), + page_size=page_size, + ) + ) + + def grant_access(self, subject_type: SubjectType, subject_id: str) -> AccessGrant: + """Grant a subject VIEWER access to this run. + + Args: + subject_type: The type of subject to grant access to (e.g. + ``ORGANIZATION_USER``, ``SHARE_TOKEN``). + subject_id: The ID of the subject to grant access to. + + Returns: + AccessGrant: The created grant. + + Raises: + Exception: If the API request fails after all retries. + """ + grant = Retrying( + retry=retry_if_exception_type(exception_types=RETRYABLE_EXCEPTIONS), + stop=stop_after_attempt(settings().run_retry_attempts), + wait=wait_exponential_jitter(initial=settings().run_retry_wait_min, max=settings().run_retry_wait_max), + before_sleep=_log_retry_attempt, + reraise=True, + )( + lambda: self._api.create_grant_v1_access_grants_post( + grant_create_request=GrantCreateRequest( + resource_type=ResourceType.RUN, + resource_id=self.run_id, + subject_type=subject_type, + subject_id=subject_id, + relation=GrantRelation.VIEWER, + ), + _request_timeout=settings().run_timeout, + _headers={"User-Agent": user_agent()}, + ) + ) + operation_cache_clear() + return AccessGrant(api=self._api, **grant.__dict__) + def __str__(self) -> str: """Returns a string representation of the application run. diff --git a/tests/aignostics/application/cli_test.py b/tests/aignostics/application/cli_test.py index 567e0f29c..b00ae7f4a 100644 --- a/tests/aignostics/application/cli_test.py +++ b/tests/aignostics/application/cli_test.py @@ -2182,3 +2182,489 @@ def test_cli_application_version_document_download_failed(runner: CliRunner, tmp output = normalize_output(result.output) assert "Failed to download release document" in output assert DOCUMENT_TEST_FAILURE_MESSAGE in output + + +# ───────────────────────────────────────────────────────────────────────────── +# run share status +# ───────────────────────────────────────────────────────────────────────────── + +APPLICATION_CLI_SERVICE_PATCH_TARGET = "aignostics.application._cli.Service" + + +def _make_mock_grant( + grant_id: str = "grant-001", + subject_id: str = "org-abc", + relation_value: str = "VIEWER", + created_at: datetime | None = None, +) -> MagicMock: + grant = MagicMock() + grant.grant_id = grant_id + grant.subject_id = subject_id + grant.relation = MagicMock() + grant.relation.value = relation_value + grant.created_at = created_at or datetime(2025, 1, 1, tzinfo=UTC) + grant.model_dump.return_value = {"grant_id": grant_id, "subject_id": subject_id} + return grant + + +def _make_mock_token( + share_token_id: str = "tok-001", # noqa: S107 + share_token: str = "secret-value", # noqa: S107 + created_at: datetime | None = None, + expires_at: datetime | None = None, +) -> MagicMock: + token = MagicMock() + token.share_token_id = share_token_id + token.share_token = share_token + token.created_at = created_at or datetime(2025, 1, 1, tzinfo=UTC) + token.expires_at = expires_at + token.model_dump.return_value = {"share_token_id": share_token_id} + return token + + +@pytest.mark.integration +def test_cli_run_share_status_empty_text(runner: CliRunner) -> None: + """Share status prints section headers even when there are no grants or tokens.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.return_value = iter([]) + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([]) + result = runner.invoke(cli, ["application", "run", "share", "status", "run-001"]) + assert result.exit_code == 0 + output = normalize_output(result.output) + assert "Organization grants" in output + assert "Share tokens" in output + + +@pytest.mark.integration +def test_cli_run_share_status_with_data_text(runner: CliRunner) -> None: + """Share status renders grant and token IDs in text mode.""" + grant = _make_mock_grant() + token = _make_mock_token() + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.return_value = iter([grant]) + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([token]) + result = runner.invoke(cli, ["application", "run", "share", "status", "run-001"]) + assert result.exit_code == 0 + output = normalize_output(result.output) + assert "grant-001" in output + assert "tok-001" in output + + +@pytest.mark.integration +def test_cli_run_share_status_json(runner: CliRunner) -> None: + """Share status --format json returns parseable JSON with both sections.""" + grant = _make_mock_grant() + token = _make_mock_token() + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.return_value = iter([grant]) + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([token]) + result = runner.invoke(cli, ["application", "run", "share", "status", "run-001", "--format", "json"]) + assert result.exit_code == 0 + data = json.loads(result.stdout) + assert "organization_grants" in data + assert "share_tokens" in data + assert data["organization_grants"][0]["grant_id"] == "grant-001" + assert data["share_tokens"][0]["share_token_id"] == "tok-001" # noqa: S105 + + +@pytest.mark.integration +def test_cli_run_share_status_not_found(runner: CliRunner) -> None: + """Share status exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.side_effect = ApiNotFound( + status=404, reason="Not Found" + ) + result = runner.invoke(cli, ["application", "run", "share", "status", "bad-run"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_status_error(runner: CliRunner) -> None: + """Share status exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.side_effect = RuntimeError("boom") + result = runner.invoke(cli, ["application", "run", "share", "status", "run-001"]) + assert result.exit_code == 1 + assert "Failed to retrieve share status" in normalize_output(result.output) + + +# ───────────────────────────────────────────────────────────────────────────── +# run share organization list +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +def test_cli_run_share_organization_list_empty_text(runner: CliRunner) -> None: + """Organization list prints 'No active organization grants' when empty.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.return_value = iter([]) + result = runner.invoke(cli, ["application", "run", "share", "organization", "list", "run-001"]) + assert result.exit_code == 0 + assert "No active organization grants" in normalize_output(result.output) + + +@pytest.mark.integration +def test_cli_run_share_organization_list_with_data_text(runner: CliRunner) -> None: + """Organization list renders grant details in text mode.""" + grant = _make_mock_grant(grant_id="grant-xyz", subject_id="org-123") + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.return_value = iter([grant]) + result = runner.invoke(cli, ["application", "run", "share", "organization", "list", "run-001"]) + assert result.exit_code == 0 + output = normalize_output(result.output) + assert "grant-xyz" in output + assert "org-123" in output + + +@pytest.mark.integration +def test_cli_run_share_organization_list_json(runner: CliRunner) -> None: + """Organization list --format json returns a JSON array.""" + grant = _make_mock_grant() + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.return_value = iter([grant]) + result = runner.invoke( + cli, ["application", "run", "share", "organization", "list", "run-001", "--format", "json"] + ) + assert result.exit_code == 0 + data = json.loads(result.stdout) + assert isinstance(data, list) + assert data[0]["grant_id"] == "grant-001" + + +@pytest.mark.integration +def test_cli_run_share_organization_list_not_found(runner: CliRunner) -> None: + """Organization list exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.side_effect = ApiNotFound( + status=404, reason="Not Found" + ) + result = runner.invoke(cli, ["application", "run", "share", "organization", "list", "bad-run"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_organization_list_error(runner: CliRunner) -> None: + """Organization list exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_organization_grants.side_effect = RuntimeError("kaboom") + result = runner.invoke(cli, ["application", "run", "share", "organization", "list", "run-001"]) + assert result.exit_code == 1 + assert "Failed to list organization grants" in normalize_output(result.output) + + +# ───────────────────────────────────────────────────────────────────────────── +# run share organization grant +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +def test_cli_run_share_organization_grant_text(runner: CliRunner) -> None: + """Organization grant prints confirmation in text mode.""" + grant = _make_mock_grant(grant_id="grant-new") + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_with_organization.return_value = grant + result = runner.invoke(cli, ["application", "run", "share", "organization", "grant", "run-001", "org-abc"]) + assert result.exit_code == 0 + output = normalize_output(result.output) + assert "run-001" in output + assert "grant-new" in output + + +@pytest.mark.integration +def test_cli_run_share_organization_grant_text_no_org(runner: CliRunner) -> None: + """Organization grant without explicit org_id delegates to default organization.""" + grant = _make_mock_grant(grant_id="grant-default") + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_with_organization.return_value = grant + result = runner.invoke(cli, ["application", "run", "share", "organization", "grant", "run-001"]) + assert result.exit_code == 0 + mock_svc_cls.return_value.application_run_share_with_organization.assert_called_once_with( + "run-001", organization_id=None + ) + + +@pytest.mark.integration +def test_cli_run_share_organization_grant_json(runner: CliRunner) -> None: + """Organization grant --format json returns parseable JSON.""" + grant = _make_mock_grant() + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_with_organization.return_value = grant + result = runner.invoke( + cli, + ["application", "run", "share", "organization", "grant", "run-001", "org-abc", "--format", "json"], + ) + assert result.exit_code == 0 + data = json.loads(result.stdout) + assert data["grant_id"] == "grant-001" + + +@pytest.mark.integration +def test_cli_run_share_organization_grant_not_found(runner: CliRunner) -> None: + """Organization grant exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_with_organization.side_effect = ApiNotFound( + status=404, reason="Not Found" + ) + result = runner.invoke(cli, ["application", "run", "share", "organization", "grant", "bad-run", "org-abc"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_organization_grant_error(runner: CliRunner) -> None: + """Organization grant exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_with_organization.side_effect = RuntimeError("fail") + result = runner.invoke(cli, ["application", "run", "share", "organization", "grant", "run-001", "org-abc"]) + assert result.exit_code == 1 + assert "Failed to share run" in normalize_output(result.output) + + +# ───────────────────────────────────────────────────────────────────────────── +# run share organization revoke +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +def test_cli_run_share_organization_revoke_success(runner: CliRunner) -> None: + """Organization revoke prints confirmation.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_unshare_with_organization.return_value = None + result = runner.invoke(cli, ["application", "run", "share", "organization", "revoke", "run-001"]) + assert result.exit_code == 0 + assert "revoked" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_organization_revoke_with_org_id(runner: CliRunner) -> None: + """Organization revoke passes explicit org_id to the service.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_unshare_with_organization.return_value = None + result = runner.invoke(cli, ["application", "run", "share", "organization", "revoke", "run-001", "org-xyz"]) + assert result.exit_code == 0 + mock_svc_cls.return_value.application_run_unshare_with_organization.assert_called_once_with( + "run-001", organization_id="org-xyz" + ) + + +@pytest.mark.integration +def test_cli_run_share_organization_revoke_not_found(runner: CliRunner) -> None: + """Organization revoke exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_unshare_with_organization.side_effect = ApiNotFound( + status=404, reason="Not Found" + ) + result = runner.invoke(cli, ["application", "run", "share", "organization", "revoke", "bad-run"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_organization_revoke_error(runner: CliRunner) -> None: + """Organization revoke exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_unshare_with_organization.side_effect = RuntimeError("fail") + result = runner.invoke(cli, ["application", "run", "share", "organization", "revoke", "run-001"]) + assert result.exit_code == 1 + assert "Failed to revoke organization access" in normalize_output(result.output) + + +# ───────────────────────────────────────────────────────────────────────────── +# run share token list +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +def test_cli_run_share_token_list_empty_text(runner: CliRunner) -> None: + """Token list prints 'No active share tokens' when empty.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([]) + result = runner.invoke(cli, ["application", "run", "share", "token", "list", "run-001"]) + assert result.exit_code == 0 + assert "No active share tokens" in normalize_output(result.output) + + +@pytest.mark.integration +def test_cli_run_share_token_list_with_data_text(runner: CliRunner) -> None: + """Token list renders token IDs in text mode.""" + token = _make_mock_token(share_token_id="tok-xyz") # noqa: S106 + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([token]) + result = runner.invoke(cli, ["application", "run", "share", "token", "list", "run-001"]) + assert result.exit_code == 0 + assert "tok-xyz" in normalize_output(result.output) + + +@pytest.mark.integration +def test_cli_run_share_token_list_with_expiry(runner: CliRunner) -> None: + """Token list renders expiry date when set.""" + expires = datetime(2026, 12, 31, 23, 59, 59, tzinfo=UTC) + token = _make_mock_token(expires_at=expires) + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([token]) + result = runner.invoke(cli, ["application", "run", "share", "token", "list", "run-001"]) + assert result.exit_code == 0 + assert "2026" in normalize_output(result.output) + + +@pytest.mark.integration +def test_cli_run_share_token_list_json(runner: CliRunner) -> None: + """Token list --format json returns a JSON array.""" + token = _make_mock_token() + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_tokens.return_value = iter([token]) + result = runner.invoke(cli, ["application", "run", "share", "token", "list", "run-001", "--format", "json"]) + assert result.exit_code == 0 + data = json.loads(result.stdout) + assert isinstance(data, list) + assert data[0]["share_token_id"] == "tok-001" # noqa: S105 + + +@pytest.mark.integration +def test_cli_run_share_token_list_not_found(runner: CliRunner) -> None: + """Token list exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_tokens.side_effect = ApiNotFound(status=404, reason="Not Found") + result = runner.invoke(cli, ["application", "run", "share", "token", "list", "bad-run"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_token_list_error(runner: CliRunner) -> None: + """Token list exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_share_tokens.side_effect = RuntimeError("fail") + result = runner.invoke(cli, ["application", "run", "share", "token", "list", "run-001"]) + assert result.exit_code == 1 + assert "Failed to list share tokens" in normalize_output(result.output) + + +# ───────────────────────────────────────────────────────────────────────────── +# run share token create +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +def test_cli_run_share_token_create_text(runner: CliRunner) -> None: + """Token create prints token ID and secret once in text mode.""" + token = _make_mock_token(share_token_id="tok-new", share_token="s3cr3t") # noqa: S106 + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_create_share_token.return_value = token + result = runner.invoke(cli, ["application", "run", "share", "token", "create", "run-001"]) + assert result.exit_code == 0 + output = normalize_output(result.output) + assert "tok-new" in output + assert "s3cr3t" in output + assert "Save the token value" in output + + +@pytest.mark.integration +def test_cli_run_share_token_create_with_expiry(runner: CliRunner) -> None: + """Token create passes parsed expiry datetime to the service.""" + token = _make_mock_token(expires_at=datetime(2026, 12, 31, 23, 59, 59, tzinfo=UTC)) + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_create_share_token.return_value = token + result = runner.invoke( + cli, + [ + "application", + "run", + "share", + "token", + "create", + "run-001", + "--expires-at", + "2026-12-31T23:59:59Z", + ], + ) + assert result.exit_code == 0 + call_kwargs = mock_svc_cls.return_value.application_run_create_share_token.call_args + assert call_kwargs is not None + assert call_kwargs[1]["expires_at"] is not None or call_kwargs[0][1] is not None + + +@pytest.mark.integration +def test_cli_run_share_token_create_json(runner: CliRunner) -> None: + """Token create --format json returns parseable JSON.""" + token = _make_mock_token() + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_create_share_token.return_value = token + result = runner.invoke(cli, ["application", "run", "share", "token", "create", "run-001", "--format", "json"]) + assert result.exit_code == 0 + data = json.loads(result.stdout) + assert data["share_token_id"] == "tok-001" # noqa: S105 + + +@pytest.mark.integration +def test_cli_run_share_token_create_invalid_expiry(runner: CliRunner) -> None: + """Token create exits 1 when --expires-at is not valid ISO 8601.""" + result = runner.invoke( + cli, + ["application", "run", "share", "token", "create", "run-001", "--expires-at", "not-a-date"], + ) + assert result.exit_code == 1 + assert "Invalid --expires-at" in normalize_output(result.output) + + +@pytest.mark.integration +def test_cli_run_share_token_create_not_found(runner: CliRunner) -> None: + """Token create exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_create_share_token.side_effect = ApiNotFound( + status=404, reason="Not Found" + ) + result = runner.invoke(cli, ["application", "run", "share", "token", "create", "bad-run"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_token_create_error(runner: CliRunner) -> None: + """Token create exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_create_share_token.side_effect = RuntimeError("fail") + result = runner.invoke(cli, ["application", "run", "share", "token", "create", "run-001"]) + assert result.exit_code == 1 + assert "Failed to create share token" in normalize_output(result.output) + + +# ───────────────────────────────────────────────────────────────────────────── +# run share token revoke +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +def test_cli_run_share_token_revoke_success(runner: CliRunner) -> None: + """Token revoke prints confirmation.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_revoke_share_token.return_value = None + result = runner.invoke(cli, ["application", "run", "share", "token", "revoke", "run-001", "tok-001"]) + assert result.exit_code == 0 + output = normalize_output(result.output) + assert "tok-001" in output + assert "revoked" in output.lower() + + +@pytest.mark.integration +def test_cli_run_share_token_revoke_not_found(runner: CliRunner) -> None: + """Token revoke exits 2 when the run does not exist.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_revoke_share_token.side_effect = ApiNotFound( + status=404, reason="Not Found" + ) + result = runner.invoke(cli, ["application", "run", "share", "token", "revoke", "bad-run", "tok-001"]) + assert result.exit_code == 2 + assert "not found" in normalize_output(result.output).lower() + + +@pytest.mark.integration +def test_cli_run_share_token_revoke_error(runner: CliRunner) -> None: + """Token revoke exits 1 on an unexpected error.""" + with patch(APPLICATION_CLI_SERVICE_PATCH_TARGET) as mock_svc_cls: + mock_svc_cls.return_value.application_run_revoke_share_token.side_effect = RuntimeError("fail") + result = runner.invoke(cli, ["application", "run", "share", "token", "revoke", "run-001", "tok-001"]) + assert result.exit_code == 1 + assert "Failed to revoke share token" in normalize_output(result.output) diff --git a/tests/aignostics/application/service_test.py b/tests/aignostics/application/service_test.py index ea023455b..23a0e404e 100644 --- a/tests/aignostics/application/service_test.py +++ b/tests/aignostics/application/service_test.py @@ -565,3 +565,245 @@ def test_application_run_update_item_custom_metadata_not_found(mock_get_client: with pytest.raises(NotFoundException, match="not found"): service.application_run_update_item_custom_metadata("run-123", "invalid-item-id", {"key": "value"}) + + +# ───────────────────────────────────────────────────────────────────────────── +# run sharing service methods +# ───────────────────────────────────────────────────────────────────────────── + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_organization_grants_success(mock_get_client: MagicMock) -> None: + """organization_grants delegates to Run.list_share_grants with org filter.""" + mock_grant = MagicMock() + mock_run = MagicMock() + mock_run.list_share_grants.return_value = iter([mock_grant]) + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + result = list(ApplicationService().application_run_organization_grants("run-123")) + + assert result == [mock_grant] + mock_client.run.assert_called_once_with("run-123") + mock_run.list_share_grants.assert_called_once() + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_organization_grants_not_found(mock_get_client: MagicMock) -> None: + """organization_grants re-raises NotFoundException.""" + mock_run = MagicMock() + mock_run.list_share_grants.side_effect = NotFoundException("not found") + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + with pytest.raises(NotFoundException, match="not found"): + list(ApplicationService().application_run_organization_grants("run-123")) + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_organization_grants_error(mock_get_client: MagicMock) -> None: + """organization_grants wraps unexpected errors in RuntimeError.""" + mock_run = MagicMock() + mock_run.list_share_grants.side_effect = RuntimeError("boom") + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + with pytest.raises(RuntimeError, match="boom"): + list(ApplicationService().application_run_organization_grants("run-123")) + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_share_tokens_success(mock_get_client: MagicMock) -> None: + """share_tokens delegates to ShareTokens.list with run_id filter.""" + mock_token = MagicMock() + mock_client = MagicMock() + mock_client.share_tokens.list.return_value = iter([mock_token]) + mock_get_client.return_value = mock_client + + result = list(ApplicationService().application_run_share_tokens("run-123")) + + assert result == [mock_token] + mock_client.share_tokens.list.assert_called_once_with(run_id="run-123", page_size=100) + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_share_tokens_not_found(mock_get_client: MagicMock) -> None: + """share_tokens re-raises NotFoundException.""" + mock_client = MagicMock() + mock_client.share_tokens.list.side_effect = NotFoundException("not found") + mock_get_client.return_value = mock_client + + with pytest.raises(NotFoundException, match="not found"): + list(ApplicationService().application_run_share_tokens("run-123")) + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_share_with_organization_explicit_org(mock_get_client: MagicMock) -> None: + """share_with_organization calls grant_access with the given org_id.""" + mock_grant = MagicMock() + mock_run = MagicMock() + mock_run.grant_access.return_value = mock_grant + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + result = ApplicationService().application_run_share_with_organization("run-123", organization_id="org-abc") + + assert result is mock_grant + mock_client.me.assert_not_called() + mock_run.grant_access.assert_called_once() + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_share_with_organization_defaults_to_own_org(mock_get_client: MagicMock) -> None: + """share_with_organization fetches own org_id when none is provided.""" + mock_grant = MagicMock() + mock_run = MagicMock() + mock_run.grant_access.return_value = mock_grant + mock_me = MagicMock() + mock_me.organization.id = "own-org" + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_client.me.return_value = mock_me + mock_get_client.return_value = mock_client + + result = ApplicationService().application_run_share_with_organization("run-123", organization_id=None) + + assert result is mock_grant + mock_client.me.assert_called_once() + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_share_with_organization_not_found(mock_get_client: MagicMock) -> None: + """share_with_organization re-raises NotFoundException.""" + mock_run = MagicMock() + mock_run.grant_access.side_effect = NotFoundException("not found") + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + with pytest.raises(NotFoundException, match="not found"): + ApplicationService().application_run_share_with_organization("run-123", organization_id="org-abc") + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_unshare_with_organization_revokes_grants(mock_get_client: MagicMock) -> None: + """unshare_with_organization revokes all matching grants.""" + mock_grant = MagicMock() + mock_run = MagicMock() + mock_run.list_share_grants.return_value = iter([mock_grant]) + mock_me = MagicMock() + mock_me.organization.id = "own-org" + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_client.me.return_value = mock_me + mock_get_client.return_value = mock_client + + ApplicationService().application_run_unshare_with_organization("run-123", organization_id=None) + + mock_grant.revoke.assert_called_once() + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_unshare_with_organization_not_found(mock_get_client: MagicMock) -> None: + """unshare_with_organization re-raises NotFoundException.""" + mock_run = MagicMock() + mock_run.list_share_grants.side_effect = NotFoundException("not found") + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + with pytest.raises(NotFoundException, match="not found"): + ApplicationService().application_run_unshare_with_organization("run-123", organization_id="org-abc") + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_create_share_token_success(mock_get_client: MagicMock) -> None: + """create_share_token creates a token and grants it access to the run.""" + mock_token = MagicMock() + mock_token.share_token_id = "tok-001" # noqa: S105 + mock_run = MagicMock() + mock_client = MagicMock() + mock_client.share_tokens.create.return_value = mock_token + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + result = ApplicationService().application_run_create_share_token("run-123") + + assert result is mock_token + mock_client.share_tokens.create.assert_called_once_with(expires_at=None) + mock_run.grant_access.assert_called_once() + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_create_share_token_with_expiry(mock_get_client: MagicMock) -> None: + """create_share_token passes expiry datetime through to ShareTokens.create.""" + mock_token = MagicMock() + mock_run = MagicMock() + mock_client = MagicMock() + mock_client.share_tokens.create.return_value = mock_token + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + expiry = datetime(2026, 12, 31, 23, 59, 59, tzinfo=UTC) + ApplicationService().application_run_create_share_token("run-123", expires_at=expiry) + + mock_client.share_tokens.create.assert_called_once_with(expires_at=expiry) + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_create_share_token_not_found(mock_get_client: MagicMock) -> None: + """create_share_token re-raises NotFoundException.""" + mock_client = MagicMock() + mock_client.share_tokens.create.side_effect = NotFoundException("not found") + mock_get_client.return_value = mock_client + + with pytest.raises(NotFoundException, match="not found"): + ApplicationService().application_run_create_share_token("run-123") + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_revoke_share_token_success(mock_get_client: MagicMock) -> None: + """revoke_share_token finds the grant on the run and revokes it.""" + mock_grant = MagicMock() + mock_run = MagicMock() + mock_run.list_share_grants.return_value = iter([mock_grant]) + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + ApplicationService().application_run_revoke_share_token("run-123", "tok-001") + + mock_run.list_share_grants.assert_called_once() + mock_grant.revoke.assert_called_once() + + +@pytest.mark.unit +@patch("aignostics.application._service.Service._get_platform_client") +def test_application_run_revoke_share_token_not_found(mock_get_client: MagicMock) -> None: + """revoke_share_token raises NotFoundException when no grant exists for the token.""" + mock_run = MagicMock() + mock_run.list_share_grants.return_value = iter([]) + mock_client = MagicMock() + mock_client.run.return_value = mock_run + mock_get_client.return_value = mock_client + + with pytest.raises(NotFoundException, match="No grant found"): + ApplicationService().application_run_revoke_share_token("run-123", "tok-missing") diff --git a/tests/aignostics/platform/resources/access_test.py b/tests/aignostics/platform/resources/access_test.py new file mode 100644 index 000000000..448fdfb1a --- /dev/null +++ b/tests/aignostics/platform/resources/access_test.py @@ -0,0 +1,358 @@ +"""Unit tests for access control resources: AccessGrant, ShareToken, ShareTokens.""" + +from datetime import UTC, datetime +from unittest.mock import Mock, patch + +import pytest +from aignx.codegen.models import ( + GrantReadResponse, + GrantRelation, + ResourceType, + ShareTokenCreateRequest, + ShareTokenCreateResponse, + ShareTokenReadResponse, + SubjectType, +) + +from aignostics.platform._api import _AuthenticatedApi +from aignostics.platform.resources.access import ( + AccessGrant, + ShareToken, + ShareTokens, +) + +_GRANT_ID = "grant-001" +_TOKEN_ID = "token-001" # noqa: S105 +_TOKEN_SECRET = "secret-token-value" # noqa: S105 +_ORG_ID = "org-001" +_SUBJECT_ID = "subject-001" +_RUN_ID = "run-001" +_CREATED_AT = datetime(2024, 1, 1, tzinfo=UTC) + + +@pytest.fixture +def mock_api() -> Mock: + """Return a mock _AuthenticatedApi.""" + api = Mock(spec=_AuthenticatedApi) + api.token_provider = lambda: "test-token" + return api + + +@pytest.fixture +def share_tokens_resource(mock_api: Mock) -> ShareTokens: + """Return a ShareTokens resource bound to the mock API.""" + return ShareTokens(mock_api) + + +def _make_grant_read_response( + grant_id: str = _GRANT_ID, + subject_type: SubjectType = SubjectType.SHARE_TOKEN, + subject_id: str = _TOKEN_ID, + revoked: bool = False, +) -> GrantReadResponse: + return GrantReadResponse( + grant_id=grant_id, + resource_type=ResourceType.RUN, + resource_id=_RUN_ID, + subject_type=subject_type, + subject_id=subject_id, + relation=GrantRelation.VIEWER, + created_by="user-1", + created_at=_CREATED_AT, + revoked=revoked, + ) + + +def _make_share_token_read_response(token_id: str = _TOKEN_ID) -> ShareTokenReadResponse: + return ShareTokenReadResponse( + share_token_id=token_id, + created_at=_CREATED_AT, + expires_at=None, + revoked=False, + ) + + +def _make_share_token_create_response( + token_id: str = _TOKEN_ID, + expires_at: datetime | None = None, +) -> ShareTokenCreateResponse: + return ShareTokenCreateResponse( + share_token_id=token_id, + share_token=_TOKEN_SECRET, + created_at=_CREATED_AT, + expires_at=expires_at, + revoked=False, + ) + + +class TestAccessGrantRevoke: + """Tests for AccessGrant.revoke().""" + + @pytest.mark.unit + @staticmethod + def test_revoke_calls_api_with_grant_id(mock_api: Mock) -> None: + """revoke() calls the revoke endpoint with the correct grant_id.""" + grant = AccessGrant( + api=mock_api, + grant_id=_GRANT_ID, + resource_type=ResourceType.RUN, + resource_id=_RUN_ID, + subject_id=_SUBJECT_ID, + subject_type=SubjectType.ORGANIZATION_USER, + relation=GrantRelation.VIEWER, + created_by="user-1", + created_at=_CREATED_AT, + revoked=False, + ) + + with patch("aignostics.platform.resources.access.operation_cache_clear"): + grant.revoke() + + call_kw = mock_api.revoke_grant_v1_access_grants_grant_id_delete.call_args.kwargs + mock_api.revoke_grant_v1_access_grants_grant_id_delete.assert_called_once_with( + grant_id=_GRANT_ID, + _request_timeout=call_kw["_request_timeout"], + _headers={"User-Agent": call_kw["_headers"]["User-Agent"]}, + ) + + @pytest.mark.unit + @staticmethod + def test_revoke_clears_operation_cache(mock_api: Mock) -> None: + """revoke() clears the operation cache after the API call.""" + grant = AccessGrant( + api=mock_api, + grant_id=_GRANT_ID, + resource_type=ResourceType.RUN, + resource_id=_RUN_ID, + subject_id=_SUBJECT_ID, + subject_type=SubjectType.ORGANIZATION_USER, + relation=GrantRelation.VIEWER, + created_by="user-1", + created_at=_CREATED_AT, + revoked=False, + ) + + with patch("aignostics.platform.resources.access.operation_cache_clear") as mock_clear: + grant.revoke() + + mock_clear.assert_called_once() + + +class TestShareTokenForTokenId: + """Tests for ShareToken.for_token_id() classmethod.""" + + @pytest.mark.unit + @staticmethod + def test_calls_api_with_token_id(mock_api: Mock) -> None: + """for_token_id() calls the get_share_token endpoint with the given ID.""" + mock_api.get_share_token_v1_access_share_tokens_share_token_id_get.return_value = ( + _make_share_token_read_response() + ) + + with patch("aignostics.platform._client.Client") as mock_client_cls: + mock_client_cls.get_api_client.return_value = mock_api + ShareToken.for_token_id(_TOKEN_ID) + + call_kw = mock_api.get_share_token_v1_access_share_tokens_share_token_id_get.call_args.kwargs + mock_api.get_share_token_v1_access_share_tokens_share_token_id_get.assert_called_once_with( + share_token_id=_TOKEN_ID, + _request_timeout=call_kw["_request_timeout"], + _headers={"User-Agent": call_kw["_headers"]["User-Agent"]}, + ) + + @pytest.mark.unit + @staticmethod + def test_uses_cached_api_client_by_default(mock_api: Mock) -> None: + """for_token_id() calls get_api_client with cache_token=True by default.""" + mock_api.get_share_token_v1_access_share_tokens_share_token_id_get.return_value = ( + _make_share_token_read_response() + ) + + with patch("aignostics.platform._client.Client") as mock_client_cls: + mock_client_cls.get_api_client.return_value = mock_api + ShareToken.for_token_id(_TOKEN_ID) + + mock_client_cls.get_api_client.assert_called_once_with(cache_token=True) + + @pytest.mark.unit + @staticmethod + def test_cache_token_false_forwarded(mock_api: Mock) -> None: + """for_token_id(cache_token=False) passes cache_token=False to get_api_client.""" + mock_api.get_share_token_v1_access_share_tokens_share_token_id_get.return_value = ( + _make_share_token_read_response() + ) + + with patch("aignostics.platform._client.Client") as mock_client_cls: + mock_client_cls.get_api_client.return_value = mock_api + ShareToken.for_token_id(_TOKEN_ID, cache_token=False) + + mock_client_cls.get_api_client.assert_called_once_with(cache_token=False) + + @pytest.mark.unit + @staticmethod + def test_returns_share_token_with_correct_fields(mock_api: Mock) -> None: + """for_token_id() returns a ShareToken constructed from the API response.""" + mock_api.get_share_token_v1_access_share_tokens_share_token_id_get.return_value = ( + _make_share_token_read_response() + ) + + with patch("aignostics.platform._client.Client") as mock_client_cls: + mock_client_cls.get_api_client.return_value = mock_api + result = ShareToken.for_token_id(_TOKEN_ID) + + assert isinstance(result, ShareToken) + assert result.share_token_id == _TOKEN_ID + assert result.created_at == _CREATED_AT + assert result.revoked is False + assert result.share_token is None # Secret absent in read responses + + +class TestShareTokenRevoke: + """Tests for ShareToken.revoke().""" + + @pytest.mark.unit + @staticmethod + def test_revoke_calls_api_with_token_id(mock_api: Mock) -> None: + """revoke() calls the revoke endpoint with the correct share_token_id.""" + token = ShareToken(api=mock_api, share_token_id=_TOKEN_ID, revoked=False, created_at=_CREATED_AT) + + with patch("aignostics.platform.resources.access.operation_cache_clear"): + token.revoke() + + call_kw = mock_api.revoke_share_token_v1_access_share_tokens_share_token_id_delete.call_args.kwargs + mock_api.revoke_share_token_v1_access_share_tokens_share_token_id_delete.assert_called_once_with( + share_token_id=_TOKEN_ID, + _request_timeout=call_kw["_request_timeout"], + _headers={"User-Agent": call_kw["_headers"]["User-Agent"]}, + ) + + @pytest.mark.unit + @staticmethod + def test_revoke_clears_operation_cache(mock_api: Mock) -> None: + """revoke() clears the operation cache after the API call.""" + token = ShareToken(api=mock_api, share_token_id=_TOKEN_ID, revoked=False, created_at=_CREATED_AT) + + with patch("aignostics.platform.resources.access.operation_cache_clear") as mock_clear: + token.revoke() + + mock_clear.assert_called_once() + + +class TestShareTokensList: + """Tests for ShareTokens.list().""" + + @pytest.mark.unit + @staticmethod + def test_returns_share_tokens(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """list() returns ShareToken objects from the API response.""" + mock_api.list_share_tokens_v1_access_share_tokens_get.return_value = [_make_share_token_read_response()] + + result = list(share_tokens_resource.list()) + + assert len(result) == 1 + assert isinstance(result[0], ShareToken) + assert result[0].share_token_id == _TOKEN_ID + assert result[0].share_token is None # Secrets are absent in read responses + + @pytest.mark.unit + @staticmethod + def test_returns_empty_list_when_none(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """list() returns an empty iterator when the API returns no tokens.""" + mock_api.list_share_tokens_v1_access_share_tokens_get.return_value = [] + + assert list(share_tokens_resource.list()) == [] + + @pytest.mark.unit + @staticmethod + def test_multiple_tokens_returned(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """list() returns all tokens from the API response.""" + responses = [_make_share_token_read_response(f"token-{i}") for i in range(3)] + mock_api.list_share_tokens_v1_access_share_tokens_get.return_value = responses + + result = list(share_tokens_resource.list()) + + assert len(result) == 3 + assert all(isinstance(t, ShareToken) for t in result) + assert {t.share_token_id for t in result} == {"token-0", "token-1", "token-2"} + + @pytest.mark.unit + @staticmethod + def test_nocache_bypasses_cache_and_fetches_fresh_data(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """list(nocache=True) bypasses the cache and calls the API again.""" + first = _make_share_token_read_response("token-first") + second = _make_share_token_read_response("token-second") + mock_api.list_share_tokens_v1_access_share_tokens_get.side_effect = [[first], [second]] + + result1 = list(share_tokens_resource.list()) + result2 = list(share_tokens_resource.list(nocache=True)) + + assert result1[0].share_token_id == "token-first" # noqa: S105 + assert result2[0].share_token_id == "token-second" # noqa: S105 + assert mock_api.list_share_tokens_v1_access_share_tokens_get.call_count == 2 + + @pytest.mark.unit + @staticmethod + def test_default_list_uses_cache_on_second_call(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """list() without nocache returns cached result on the second call.""" + mock_api.list_share_tokens_v1_access_share_tokens_get.return_value = [_make_share_token_read_response()] + + list(share_tokens_resource.list()) + list(share_tokens_resource.list()) + + mock_api.list_share_tokens_v1_access_share_tokens_get.assert_called_once() + + +class TestShareTokensCreate: + """Tests for ShareTokens.create().""" + + @pytest.mark.unit + @staticmethod + def test_create_returns_share_token_with_secret(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """create() returns a ShareToken that includes the one-time token secret.""" + mock_api.create_share_token_v1_access_share_tokens_post.return_value = _make_share_token_create_response() + + result = share_tokens_resource.create() + + assert isinstance(result, ShareToken) + assert result.share_token_id == _TOKEN_ID + assert result.share_token == _TOKEN_SECRET + + @pytest.mark.unit + @staticmethod + def test_create_without_expires_at_passes_none(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """create() passes expires_at=None to the API when not specified.""" + mock_api.create_share_token_v1_access_share_tokens_post.return_value = _make_share_token_create_response() + + share_tokens_resource.create() + + call_kw = mock_api.create_share_token_v1_access_share_tokens_post.call_args.kwargs + req: ShareTokenCreateRequest = call_kw["share_token_create_request"] + assert req.expires_at is None + + @pytest.mark.unit + @staticmethod + def test_create_with_expires_at_forwards_value(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """create(expires_at=...) forwards the expiry to the API and returns it on the token.""" + expires = datetime(2025, 12, 31, tzinfo=UTC) + mock_api.create_share_token_v1_access_share_tokens_post.return_value = _make_share_token_create_response( + expires_at=expires + ) + + result = share_tokens_resource.create(expires_at=expires) + + call_kw = mock_api.create_share_token_v1_access_share_tokens_post.call_args.kwargs + req: ShareTokenCreateRequest = call_kw["share_token_create_request"] + assert req.expires_at == expires + assert result.expires_at == expires + + @pytest.mark.unit + @staticmethod + def test_create_returns_token_with_correct_metadata(share_tokens_resource: ShareTokens, mock_api: Mock) -> None: + """create() maps all fields from the API response onto the returned ShareToken.""" + mock_api.create_share_token_v1_access_share_tokens_post.return_value = _make_share_token_create_response() + + result = share_tokens_resource.create() + + assert result.created_at == _CREATED_AT + assert result.revoked is False + assert result.expires_at is None diff --git a/tests/aignostics/platform/resources/run_sharing_test.py b/tests/aignostics/platform/resources/run_sharing_test.py new file mode 100644 index 000000000..02c286514 --- /dev/null +++ b/tests/aignostics/platform/resources/run_sharing_test.py @@ -0,0 +1,234 @@ +"""Unit tests for Run sharing methods: list_share_grants and grant_access.""" + +from datetime import UTC, datetime +from unittest.mock import Mock, patch + +import pytest +from aignx.codegen.models import ( + GrantCreateRequest, + GrantReadResponse, + GrantRelation, + ResourceType, + SubjectType, +) + +from aignostics.platform._api import _AuthenticatedApi +from aignostics.platform.resources.access import AccessGrant +from aignostics.platform.resources.runs import Run + +_RUN_ID = "550e8400-e29b-41d4-a716-446655440000" +_ORG_ID = "org-001" +_GRANT_ID = "grant-001" +_CREATED_AT = datetime(2024, 1, 1, tzinfo=UTC) + + +@pytest.fixture +def mock_api() -> Mock: + """Return a mock _AuthenticatedApi.""" + api = Mock(spec=_AuthenticatedApi) + api.token_provider = lambda: "test-token" + return api + + +@pytest.fixture +def run(mock_api: Mock) -> Run: + """Return a Run bound to the mock API.""" + return Run(mock_api, _RUN_ID) + + +def _make_grant_response( + grant_id: str = _GRANT_ID, + subject_type: SubjectType = SubjectType.ORGANIZATION_USER, + subject_id: str = _ORG_ID, + revoked: bool = False, +) -> GrantReadResponse: + return GrantReadResponse( + grant_id=grant_id, + resource_type=ResourceType.RUN, + resource_id=_RUN_ID, + subject_type=subject_type, + subject_id=subject_id, + relation=GrantRelation.VIEWER, + created_by="user-1", + created_at=_CREATED_AT, + revoked=revoked, + ) + + +class TestRunListShareGrants: + """Tests for Run.list_share_grants().""" + + @pytest.mark.unit + @staticmethod + def test_returns_access_grants(run: Run, mock_api: Mock) -> None: + """list_share_grants() yields AccessGrant objects from the API response.""" + mock_api.list_grants_v1_access_grants_get.return_value = [_make_grant_response()] + + result = list(run.list_share_grants()) + + assert len(result) == 1 + assert isinstance(result[0], AccessGrant) + assert result[0].grant_id == _GRANT_ID + assert result[0].relation == GrantRelation.VIEWER + + @pytest.mark.unit + @staticmethod + def test_calls_api_with_run_resource_params(run: Run, mock_api: Mock) -> None: + """list_share_grants() passes resource_type=RUN and the run's ID to the API.""" + mock_api.list_grants_v1_access_grants_get.return_value = [] + + list(run.list_share_grants()) + + call_kw = mock_api.list_grants_v1_access_grants_get.call_args.kwargs + assert call_kw["resource_type"] == ResourceType.RUN + assert call_kw["resource_id"] == _RUN_ID + assert call_kw["revoked"] is False + + @pytest.mark.unit + @staticmethod + def test_default_filters_are_none(run: Run, mock_api: Mock) -> None: + """list_share_grants() passes subject_type=None and subject_id=None by default.""" + mock_api.list_grants_v1_access_grants_get.return_value = [] + + list(run.list_share_grants()) + + call_kw = mock_api.list_grants_v1_access_grants_get.call_args.kwargs + assert call_kw["subject_type"] is None + assert call_kw["subject_id"] is None + + @pytest.mark.unit + @staticmethod + def test_passes_subject_type_filter(run: Run, mock_api: Mock) -> None: + """list_share_grants(subject_type=...) forwards the filter to the API.""" + mock_api.list_grants_v1_access_grants_get.return_value = [] + + list(run.list_share_grants(subject_type=SubjectType.SHARE_TOKEN)) + + call_kw = mock_api.list_grants_v1_access_grants_get.call_args.kwargs + assert call_kw["subject_type"] == SubjectType.SHARE_TOKEN + + @pytest.mark.unit + @staticmethod + def test_passes_subject_id_filter(run: Run, mock_api: Mock) -> None: + """list_share_grants(subject_id=...) forwards the filter to the API.""" + mock_api.list_grants_v1_access_grants_get.return_value = [] + + list(run.list_share_grants(subject_id="token-abc")) + + call_kw = mock_api.list_grants_v1_access_grants_get.call_args.kwargs + assert call_kw["subject_id"] == "token-abc" + + @pytest.mark.unit + @staticmethod + def test_returns_empty_iterator_when_no_grants(run: Run, mock_api: Mock) -> None: + """list_share_grants() returns an empty iterator when the API returns no grants.""" + mock_api.list_grants_v1_access_grants_get.return_value = [] + + assert list(run.list_share_grants()) == [] + + @pytest.mark.unit + @staticmethod + def test_returns_multiple_grants(run: Run, mock_api: Mock) -> None: + """list_share_grants() returns all grants from the API response.""" + responses = [_make_grant_response(grant_id=f"grant-{i}") for i in range(3)] + mock_api.list_grants_v1_access_grants_get.return_value = responses + + result = list(run.list_share_grants()) + + assert len(result) == 3 + assert all(isinstance(g, AccessGrant) for g in result) + + @pytest.mark.unit + @staticmethod + def test_raises_for_page_size_exceeding_max(run: Run) -> None: + """list_share_grants() raises ValueError when page_size > 100.""" + with pytest.raises(ValueError, match="page_size"): + list(run.list_share_grants(page_size=101)) + + @pytest.mark.unit + @staticmethod + def test_nocache_bypasses_cache(run: Run, mock_api: Mock) -> None: + """list_share_grants(nocache=True) bypasses the cache and calls the API again.""" + first = [_make_grant_response(grant_id="grant-first")] + second = [_make_grant_response(grant_id="grant-second")] + mock_api.list_grants_v1_access_grants_get.side_effect = [first, second] + + result1 = list(run.list_share_grants()) + result2 = list(run.list_share_grants(nocache=True)) + + assert result1[0].grant_id == "grant-first" + assert result2[0].grant_id == "grant-second" + assert mock_api.list_grants_v1_access_grants_get.call_count == 2 + + @pytest.mark.unit + @staticmethod + def test_default_uses_cache_on_second_call(run: Run, mock_api: Mock) -> None: + """list_share_grants() without nocache returns cached result on the second call.""" + mock_api.list_grants_v1_access_grants_get.return_value = [_make_grant_response()] + + list(run.list_share_grants()) + list(run.list_share_grants()) + + mock_api.list_grants_v1_access_grants_get.assert_called_once() + + +class TestRunGrantAccess: + """Tests for Run.grant_access().""" + + @pytest.mark.unit + @staticmethod + def test_creates_grant_with_correct_request(run: Run, mock_api: Mock) -> None: + """grant_access() calls create_grant with the correct GrantCreateRequest fields.""" + mock_api.create_grant_v1_access_grants_post.return_value = _make_grant_response() + + with patch("aignostics.platform.resources.runs.operation_cache_clear"): + run.grant_access(subject_type=SubjectType.ORGANIZATION_USER, subject_id=_ORG_ID) + + req: GrantCreateRequest = mock_api.create_grant_v1_access_grants_post.call_args.kwargs["grant_create_request"] + assert req.resource_type == ResourceType.RUN + assert req.resource_id == _RUN_ID + assert req.subject_type == SubjectType.ORGANIZATION_USER + assert req.subject_id == _ORG_ID + assert req.relation == GrantRelation.VIEWER + + @pytest.mark.unit + @staticmethod + def test_returns_access_grant(run: Run, mock_api: Mock) -> None: + """grant_access() returns an AccessGrant built from the API response.""" + mock_api.create_grant_v1_access_grants_post.return_value = _make_grant_response() + + with patch("aignostics.platform.resources.runs.operation_cache_clear"): + result = run.grant_access(subject_type=SubjectType.ORGANIZATION_USER, subject_id=_ORG_ID) + + assert isinstance(result, AccessGrant) + assert result.grant_id == _GRANT_ID + assert result.subject_id == _ORG_ID + assert result.relation == GrantRelation.VIEWER + + @pytest.mark.unit + @staticmethod + def test_clears_operation_cache(run: Run, mock_api: Mock) -> None: + """grant_access() clears the operation cache after creating the grant.""" + mock_api.create_grant_v1_access_grants_post.return_value = _make_grant_response() + + with patch("aignostics.platform.resources.runs.operation_cache_clear") as mock_clear: + run.grant_access(subject_type=SubjectType.ORGANIZATION_USER, subject_id=_ORG_ID) + + mock_clear.assert_called_once() + + @pytest.mark.unit + @staticmethod + def test_works_with_share_token_subject_type(run: Run, mock_api: Mock) -> None: + """grant_access() accepts SubjectType.SHARE_TOKEN and forwards it correctly.""" + token_id = "token-abc" # noqa: S105 + mock_api.create_grant_v1_access_grants_post.return_value = _make_grant_response( + subject_type=SubjectType.SHARE_TOKEN, subject_id=token_id + ) + + with patch("aignostics.platform.resources.runs.operation_cache_clear"): + result = run.grant_access(subject_type=SubjectType.SHARE_TOKEN, subject_id=token_id) + + req: GrantCreateRequest = mock_api.create_grant_v1_access_grants_post.call_args.kwargs["grant_create_request"] + assert req.subject_type == SubjectType.SHARE_TOKEN + assert req.subject_id == token_id + assert isinstance(result, AccessGrant) diff --git a/tests/constants_test.py b/tests/constants_test.py index 8ace5dbfa..854df9da5 100644 --- a/tests/constants_test.py +++ b/tests/constants_test.py @@ -85,6 +85,8 @@ ("tissue_qc_parquet_polygons.parquet", 39435, 10), ("tissue_segmentation_parquet_polygons.parquet", 117509, 10), ("cell_classification_parquet_polygons.parquet", 1985592, 10), + ("cell_detection_parquet_polygons.parquet", 1978790, 10), + ("cell_detection_parquet_centers.parquet", 657740, 10), ] SPOT_0_EXPECTED_CELLS_CLASSIFIED = (39798, 10) @@ -101,6 +103,8 @@ ("tissue_qc_parquet_polygons.parquet", 29087, 10), ("tissue_segmentation_parquet_polygons.parquet", 56563, 10), ("cell_classification_parquet_polygons.parquet", 562536, 10), + ("cell_detection_parquet_polygons.parquet", 1985614, 0), + ("cell_detection_parquet_centers.parquet", 657740, 0), ] match os.getenv("AIGNOSTICS_PLATFORM_ENVIRONMENT", "production"): diff --git a/uv.lock b/uv.lock index 8d31c8666..25e8ef6ef 100644 --- a/uv.lock +++ b/uv.lock @@ -224,7 +224,7 @@ requires-dist = [ { name = "pydicom", specifier = ">=3.0.2" }, { name = "pygments", specifier = ">=2.20.0" }, { name = "pyinstaller", marker = "extra == 'pyinstaller'", specifier = ">=6.14.0,<7" }, - { name = "pyjwt", extras = ["crypto"], specifier = ">=2.12.0,<3" }, + { name = "pyjwt", extras = ["crypto"], specifier = ">=2.13.0,<3" }, { name = "python-dateutil", specifier = ">=2.9.0.post0,<3" }, { name = "python-multipart", specifier = ">=0.0.26" }, { name = "pywin32", marker = "sys_platform == 'win32'", specifier = ">=311,<312" }, @@ -264,7 +264,7 @@ dev = [ { name = "mypy", specifier = ">=1.19.0,<2" }, { name = "myst-parser", specifier = ">=5,<6" }, { name = "nox", extras = ["uv"], specifier = ">=2025.11.12" }, - { name = "pip", specifier = ">=26.1" }, + { name = "pip", specifier = ">=26.1.2" }, { name = "pip-audit", specifier = ">=2.10.0,<3" }, { name = "pip-licenses", git = "https://github.com/neXenio/pip-licenses.git?rev=master" }, { name = "pre-commit", specifier = ">=4.5.0,<5" }, @@ -5020,11 +5020,11 @@ wheels = [ [[package]] name = "pip" -version = "26.1.1" +version = "26.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/48/cb9b7a682f6fe01a4221e1728941dd4ac3cd9090a17db3779d6ff490b602/pip-26.1.1.tar.gz", hash = "sha256:d36762751d156a4ee895de8af39aa0abeeeb577f93a2eca6ab62467bbf0f8a78", size = 1840400, upload-time = "2026-05-04T19:02:21.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl", hash = "sha256:99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb", size = 1812777, upload-time = "2026-05-04T19:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" }, ] [[package]] @@ -5728,11 +5728,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.12.1" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] [package.optional-dependencies]