From 0104cf17a92ae132c7280f1c16273d51b14da087 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 09:10:49 +0300 Subject: [PATCH 01/19] refactor: enhance Capsule and search parameters with detailed metadata descriptions --- src/codeocean/capsule.py | 103 ++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 44 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index 980e8c4..8b2e7af 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from dataclasses_json import dataclass_json from typing import Optional, Iterator from requests_toolbelt.sessions import BaseUrlSession @@ -12,11 +12,13 @@ class CapsuleStatus(StrEnum): + """Status of a capsule indicating its release state.""" NonRelease = "non_release" Release = "release" class CapsuleSortBy(StrEnum): + """Fields available for sorting capsule search results.""" Created = "created" LastAccessed = "last_accessed" Name = "name" @@ -25,83 +27,91 @@ class CapsuleSortBy(StrEnum): @dataclass_json @dataclass(frozen=True) class OriginalCapsuleInfo: - id: Optional[str] = None - major_version: Optional[int] = None - minor_version: Optional[int] = None - name: Optional[str] = None - created: Optional[int] = None - public: Optional[bool] = None + """Information about the original capsule when this capsule is cloned from another.""" + id: Optional[str] = field(default=None, metadata={"description": "Original capsule ID"}) + major_version: Optional[int] = field(default=None, metadata={"description": "Original capsule major version"}) + minor_version: Optional[int] = field(default=None, metadata={"description": "Original capsule minor version"}) + name: Optional[str] = field(default=None, metadata={"description": "Original capsule name"}) + created: Optional[int] = field(default=None, metadata={"description": "Original capsule creation time (int64 timestamp)"}) + public: Optional[bool] = field(default=None, metadata={"description": "Indicates whether the original capsule is public"}) @dataclass_json @dataclass(frozen=True) class Capsule: - id: str - created: int - name: str - status: CapsuleStatus - owner: str - slug: str - article: Optional[dict] = None - cloned_from_url: Optional[str] = None - description: Optional[str] = None - field: Optional[str] = None - tags: Optional[list[str]] = None - original_capsule: Optional[OriginalCapsuleInfo] = None - release_capsule: Optional[str] = None - submission: Optional[dict] = None - versions: Optional[list[dict]] = None + """Represents a Code Ocean capsule with its metadata and properties.""" + id: str = field(metadata={"description": "Capsule ID"}) + created: int = field(metadata={"description": "Capsule creation time (int64 timestamp)"}) + name: str = field(metadata={"description": "Capsule display name"}) + status: CapsuleStatus = field(metadata={"description": "Status of the capsule (non_release or release)"}) + owner: str = field(metadata={"description": "Capsule owner's ID"}) + slug: str = field(metadata={"description": "Alternate capsule ID (URL-friendly identifier)"}) + article: Optional[dict] = field(default=None, metadata={"description": "Capsule article info with URL, ID, DOI, citation, state, name, journal_name, and publish_time"}) + cloned_from_url: Optional[str] = field(default=None, metadata={"description": "URL to external Git repository linked to capsule"}) + description: Optional[str] = field(default=None, metadata={"description": "Capsule description"}) + field: Optional[str] = field(default=None, metadata={"description": "Capsule research field"}) + tags: Optional[list[str]] = field(default=None, metadata={"description": "List of tags associated with the capsule"}) + original_capsule: Optional[OriginalCapsuleInfo] = field(default=None, metadata={"description": "Original capsule info when this is cloned from another capsule"}) + release_capsule: Optional[str] = field(default=None, metadata={"description": "Release capsule ID"}) + submission: Optional[dict] = field(default=None, metadata={"description": "Submission info with timestamp, commit hash, verification_capsule, verified status, and verified_timestamp"}) + versions: Optional[list[dict]] = field(default=None, metadata={"description": "Capsule versions with major_version, minor_version, release_time, and DOI"}) @dataclass_json @dataclass(frozen=True) class CapsuleSearchParams: - query: Optional[str] = None - next_token: Optional[str] = None - offset: Optional[int] = None - limit: Optional[int] = None - sort_field: Optional[CapsuleSortBy] = None - sort_order: Optional[SortOrder] = None - ownership: Optional[Ownership] = None - status: Optional[CapsuleStatus] = None - favorite: Optional[bool] = None - archived: Optional[bool] = None - filters: Optional[list[SearchFilter]] = None + """Parameters for searching capsules with various filters and pagination options.""" + query: Optional[str] = field(default=None, metadata={"description": "Search query in free text or structured format (name:... tag:...)"}) + next_token: Optional[str] = field(default=None, metadata={"description": "Token for next page of results from previous response"}) + offset: Optional[int] = field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) + limit: Optional[int] = field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) + sort_field: Optional[CapsuleSortBy] = field(default=None, metadata={"description": "Field to sort by (created, name, last_accessed)"}) + sort_order: Optional[SortOrder] = field(default=None, metadata={"description": "Sort order (asc or desc) - must be provided with sort_field"}) + ownership: Optional[Ownership] = field(default=None, metadata={"description": "Filter by ownership (created or shared) - defaults to all accessible"}) + status: Optional[CapsuleStatus] = field(default=None, metadata={"description": "Filter by status (release or non_release) - defaults to all"}) + favorite: Optional[bool] = field(default=None, metadata={"description": "Search only favorite capsules"}) + archived: Optional[bool] = field(default=None, metadata={"description": "Search only archived capsules"}) + filters: Optional[list[SearchFilter]] = field(default=None, metadata={"description": "Additional field-level filters for name, description, tags, or custom fields"}) @dataclass_json @dataclass(frozen=True) class CapsuleSearchResults: - has_more: bool - results: list[Capsule] - next_token: Optional[str] = None + """Results from a capsule search operation with pagination support.""" + has_more: bool = field(metadata={"description": "Indicates if there are more results available"}) + results: list[Capsule] = field(metadata={"description": "Array of capsules found matching the search criteria"}) + next_token: Optional[str] = field(default=None, metadata={"description": "Token for fetching the next page of results"}) @dataclass class Capsules: + """Client for interacting with Code Ocean capsule APIs.""" client: BaseUrlSession def get_capsule(self, capsule_id: str) -> Capsule: + """Retrieve metadata for a specific capsule by its ID.""" res = self.client.get(f"capsules/{capsule_id}") return Capsule.from_dict(res.json()) def list_computations(self, capsule_id: str) -> list[Computation]: + """Get all computations associated with a specific capsule.""" res = self.client.get(f"capsules/{capsule_id}/computations") return [Computation.from_dict(c) for c in res.json()] def update_permissions(self, capsule_id: str, permissions: Permissions): + """Update permissions for a capsule.""" self.client.post( f"capsules/{capsule_id}/permissions", json=permissions.to_dict(), ) def attach_data_assets( - self, - capsule_id: str, - attach_params: list[DataAssetAttachParams]) -> list[DataAssetAttachResults]: + self, capsule_id: str, attach_params: list[DataAssetAttachParams] + ) -> list[DataAssetAttachResults]: + """Attach one or more data assets to a capsule with optional mount paths.""" res = self.client.post( f"capsules/{capsule_id}/data_assets", json=[j.to_dict() for j in attach_params], @@ -110,22 +120,27 @@ def attach_data_assets( return [DataAssetAttachResults.from_dict(c) for c in res.json()] def detach_data_assets(self, capsule_id: str, data_assets: list[str]): + """Detach one or more data assets from a capsule by their IDs.""" self.client.delete( f"capsules/{capsule_id}/data_assets/", json=data_assets, ) - def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults: + def search_capsules( + self, search_params: CapsuleSearchParams + ) -> CapsuleSearchResults: + """Search for capsules with filtering, sorting, and pagination options.""" res = self.client.post("capsules/search", json=search_params.to_dict()) return CapsuleSearchResults.from_dict(res.json()) - def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsules]: + def search_capsules_iterator( + self, search_params: CapsuleSearchParams + ) -> Iterator[Capsule]: + """Iterate through all capsules matching search criteria with automatic pagination.""" params = search_params.to_dict() while True: - response = self.search_capsules( - search_params=CapsuleSearchParams(**params) - ) + response = self.search_capsules(search_params=CapsuleSearchParams(**params)) for result in response.results: yield result From 85b504691e786f40bdc61f3eca03905156f850e9 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 09:39:01 +0300 Subject: [PATCH 02/19] refactor: enhance documentation and metadata for computation-related classes --- src/codeocean/computation.py | 127 +++++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 43 deletions(-) diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index 33fe4ce..faf2aed 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from dataclasses_json import dataclass_json from requests_toolbelt.sessions import BaseUrlSession from typing import Optional @@ -11,6 +11,7 @@ class ComputationState(StrEnum): + """Current state of a computation during its execution lifecycle.""" Initializing = "initializing" Running = "running" Finalizing = "finalizing" @@ -19,6 +20,7 @@ class ComputationState(StrEnum): class ComputationEndStatus(StrEnum): + """Final status of a computation once it has completed execution.""" Succeeded = "succeeded" Failed = "failed" Stopped = "stopped" @@ -27,93 +29,118 @@ class ComputationEndStatus(StrEnum): @dataclass_json @dataclass(frozen=True) class Param: - name: Optional[str] = None - param_name: Optional[str] = None - value: Optional[str] = None + """Parameter information for computations with name and value.""" + name: Optional[str] = field(default=None, metadata={"description": "Parameter label/display name"}) + param_name: Optional[str] = field(default=None, metadata={"description": "Internal parameter name identifier"}) + value: Optional[str] = field(default=None, metadata={"description": "Parameter value as string"}) @dataclass_json @dataclass(frozen=True) class PipelineProcess: - name: str - capsule_id: str - version: Optional[int] = None - public: Optional[bool] = None - parameters: Optional[list[Param]] = None + """Information about a process within a pipeline execution.""" + name: str = field(metadata={"description": "Pipeline process name as it appears in main.nf"}) + capsule_id: str = field(metadata={"description": "ID of the capsule executed in this process"}) + version: Optional[int] = field(default=None, metadata={"description": "Capsule version if it's a released capsule"}) + public: Optional[bool] = field(default=None, metadata={"description": "Indicates if the capsule is a Code Ocean public app"}) + parameters: Optional[list[Param]] = field(default=None, metadata={"description": "Run parameters for this process"}) @dataclass_json @dataclass(frozen=True) class InputDataAsset: - id: str - mount: Optional[str] = None + """Data asset attached to a computation with mount information.""" + id: str = field(metadata={"description": "Attached data asset ID"}) + mount: Optional[str] = field(default=None, metadata={"description": "Mount path for the attached data asset"}) @dataclass_json @dataclass(frozen=True) class Computation: - id: str - created: int - name: str - run_time: int - state: ComputationState - cloud_workstation: Optional[bool] = None - data_assets: Optional[list[InputDataAsset]] = None - parameters: Optional[list[Param]] = None - nextflow_profile: Optional[str] = None - processes: Optional[list[PipelineProcess]] = None - end_status: Optional[ComputationEndStatus] = None - exit_code: Optional[int] = None - has_results: Optional[bool] = None + """Represents a Code Ocean computation run with its metadata and execution details.""" + id: str = field(metadata={"description": "Unique computation ID"}) + created: int = field(metadata={"description": "Computation creation time (int64 timestamp)"}) + name: str = field(metadata={"description": "Display name of the computation"}) + run_time: int = field(metadata={"description": "Total run time in seconds"}) + state: ComputationState = field(metadata={"description": "Current state of the computation (initializing, running, finalizing, completed, failed)"}) + cloud_workstation: Optional[bool] = field(default=None, metadata={"description": "Indicates whether this computation is a cloud workstation"}) + data_assets: Optional[list[InputDataAsset]] = field(default=None, metadata={"description": "List of data assets attached to this computation"}) + parameters: Optional[list[Param]] = field(default=None, metadata={"description": "Run parameters used for this computation"}) + nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile used for this computation"}) + processes: Optional[list[PipelineProcess]] = field(default=None, metadata={"description": "Pipeline processes information if this is a pipeline computation"}) + end_status: Optional[ComputationEndStatus] = field(default=None, metadata={"description": "Final status once computation is completed (succeeded, failed, stopped)"}) + exit_code: Optional[int] = field(default=None, metadata={"description": "Exit code (0 for success, non-zero for failure, 1 for pipeline errors)"}) + has_results: Optional[bool] = field(default=None, metadata={"description": "Indicates whether the computation has generated results"}) + nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile used for this computation"}) @dataclass_json @dataclass(frozen=True) class DataAssetsRunParam: - id: str - mount: str + """Data asset parameter for running computations with mount specification.""" + id: str = field(metadata={"description": "Data asset ID to attach"}) + mount: Optional[str] = field(default=None, metadata={"description": "Mount path where the data asset will be accessible"}) @dataclass_json @dataclass(frozen=True) class NamedRunParam: - param_name: str - value: str + """Named parameter for running computations with explicit parameter name.""" + param_name: str = field(metadata={"description": "Internal parameter name identifier"}) + value: str = field(metadata={"description": "Parameter value as string"}) @dataclass_json @dataclass(frozen=True) class PipelineProcessParams: - name: str - parameters: Optional[list[str]] = None - named_parameters: Optional[list[NamedRunParam]] = None + """Parameters for configuring a specific process within a pipeline execution.""" + name: str = field(metadata={"description": "Name of the pipeline process to configure"}) + parameters: Optional[list[str]] = field(default=None, metadata={"description": "Ordered list of parameter values for this process"}) + named_parameters: Optional[list[NamedRunParam]] = field(default=None, metadata={"description": "Named parameters for this process"}) @dataclass_json @dataclass(frozen=True) class RunParams: - capsule_id: Optional[str] = None - pipeline_id: Optional[str] = None - version: Optional[int] = None - resume_run_id: Optional[str] = None - nextflow_profile: Optional[str] = None - data_assets: Optional[list[DataAssetsRunParam]] = None - parameters: Optional[list[str]] = None - named_parameters: Optional[list[NamedRunParam]] = None - processes: Optional[list[PipelineProcessParams]] = None + """Complete parameter set for running capsules or pipelines with data assets and configuration.""" + capsule_id: Optional[str] = field(default=None, metadata={"description": "ID of the capsule to run (required for capsule runs)"}) + pipeline_id: Optional[str] = field(default=None, metadata={"description": "ID of the pipeline to run (required for pipeline runs)"}) + version: Optional[int] = field(default=None, metadata={"description": "Specific version of the capsule to run"}) + resume_run_id: Optional[str] = field(default=None, metadata={"description": "ID of a previous computation to resume from"}) + nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Nextflow profile to use when running the pipeline"}) + data_assets: Optional[list[DataAssetsRunParam]] = field(default=None, metadata={"description": "List of data assets to attach with their mount paths"}) + parameters: Optional[list[str]] = field(default=None, metadata={"description": "Ordered list of parameter values"}) + named_parameters: Optional[list[NamedRunParam]] = field(default=None, metadata={"description": "Named parameters for the computation"}) + processes: Optional[list[PipelineProcessParams]] = field(default=None, metadata={"description": "Process-specific parameters for pipeline runs"}) + nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile configuration"}) @dataclass class Computations: + """Client for interacting with Code Ocean computation APIs.""" client: BaseUrlSession def get_computation(self, computation_id: str) -> Computation: + """Retrieve metadata and status information for a specific computation by its ID.""" res = self.client.get(f"computations/{computation_id}") return Computation.from_dict(res.json()) def run_capsule(self, run_params: RunParams) -> Computation: + """ + Execute a capsule or pipeline with specified parameters and data assets. + + For capsule execution: Set run_params.capsule_id and optionally provide data_assets, + parameters, or named_parameters. + + For pipeline execution: Set run_params.pipeline_id and optionally provide data_assets, + processes (with process-specific parameters), and nextflow_profile configuration. + + Typical workflow: 1) run_capsule() to start execution, 2) wait_until_completed() to + monitor progress, 3) list_computation_results() and get_result_file_download_url() + to retrieve outputs. + """ res = self.client.post("computations", json=run_params.to_dict()) return Computation.from_dict(res.json()) @@ -125,9 +152,19 @@ def wait_until_completed( timeout: Optional[float] = None, ) -> Computation: """ - Polls the given computation until it reaches the 'Completed' or 'Failed' state. - - - `polling_interval` and `timeout` are in seconds + Poll a computation until it reaches 'Completed' or 'Failed' state with configurable timing. + + Args: + computation: The computation object to monitor + polling_interval: Time between status checks in seconds (minimum 5 seconds) + timeout: Maximum time to wait in seconds, or None for no timeout + + Returns: + Updated computation object once completed or failed + + Raises: + ValueError: If polling_interval < 5 or timeout constraints are violated + TimeoutError: If computation doesn't complete within the timeout period """ if polling_interval < 5: raise ValueError( @@ -154,6 +191,7 @@ def wait_until_completed( sleep(polling_interval) def list_computation_results(self, computation_id: str, path: str = "") -> Folder: + """List result files and folders generated by a computation at the specified path. Empty path retrieves the /results root folder.""" data = { "path": path, } @@ -163,6 +201,7 @@ def list_computation_results(self, computation_id: str, path: str = "") -> Folde return Folder.from_dict(res.json()) def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL: + """Generate a download URL for a specific result file from a computation.""" res = self.client.get( f"computations/{computation_id}/results/download_url", params={"path": path}, @@ -171,9 +210,11 @@ def get_result_file_download_url(self, computation_id: str, path: str) -> Downlo return DownloadFileURL.from_dict(res.json()) def delete_computation(self, computation_id: str): + """Delete a computation and stop it if currently running.""" self.client.delete(f"computations/{computation_id}") def rename_computation(self, computation_id: str, name: str): + """Rename an existing computation with a new display name.""" self.client.patch( f"computations/{computation_id}", params={"name": name} From b39f51f9c7a1f5d5fe297aeef46e76b8c977b440 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 09:39:08 +0300 Subject: [PATCH 03/19] refactor: enhance metadata descriptions for data asset classes and methods --- src/codeocean/data_asset.py | 280 +++++++++++++++++++++++------------- 1 file changed, 177 insertions(+), 103 deletions(-) diff --git a/src/codeocean/data_asset.py b/src/codeocean/data_asset.py index 6ce4db1..8e27ef2 100644 --- a/src/codeocean/data_asset.py +++ b/src/codeocean/data_asset.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses_json import dataclass_json -from dataclasses import dataclass +from dataclasses import dataclass, field from requests_toolbelt.sessions import BaseUrlSession from time import sleep, time from typing import Optional, Iterator @@ -13,6 +13,7 @@ class DataAssetType(StrEnum): + """Type of data asset indicating its content and purpose.""" Dataset = "dataset" Result = "result" Combined = "combined" @@ -20,6 +21,7 @@ class DataAssetType(StrEnum): class DataAssetState(StrEnum): + """Current state of a data asset during its creation and lifecycle.""" Draft = "draft" Ready = "ready" Failed = "failed" @@ -28,15 +30,17 @@ class DataAssetState(StrEnum): @dataclass_json @dataclass(frozen=True) class Provenance: - commit: Optional[str] = None - run_script: Optional[str] = None - docker_image: Optional[str] = None - capsule: Optional[str] = None - data_assets: Optional[list[str]] = None - computation: Optional[str] = None + """Shows the data asset provenance information when type is result.""" + commit: Optional[str] = field(default=None, metadata={"description": "Commit hash the data asset was created from"}) + run_script: Optional[str] = field(default=None, metadata={"description": "Script path the data asset was created by"}) + docker_image: Optional[str] = field(default=None, metadata={"description": "Docker image used to create the data asset"}) + capsule: Optional[str] = field(default=None, metadata={"description": "Capsule used to create the data asset"}) + data_assets: Optional[list[str]] = field(default=None, metadata={"description": "Data assets that were used to create this data asset"}) + computation: Optional[str] = field(default=None, metadata={"description": "Computation ID used to create the data asset"}) class DataAssetOrigin(StrEnum): + """Origin type of the data asset indicating where it was created from.""" Local = "local" AWS = "aws" GCP = "gcp" @@ -45,147 +49,162 @@ class DataAssetOrigin(StrEnum): @dataclass_json @dataclass(frozen=True) class SourceBucket: - origin: DataAssetOrigin - bucket: Optional[str] = None - prefix: Optional[str] = None - external: Optional[bool] = None + """Information about the bucket from which the data asset was created.""" + origin: DataAssetOrigin = field(metadata={"description": "Origin type (aws, local, gcp)"}) + bucket: Optional[str] = field(default=None, metadata={"description": "The original bucket's name"}) + prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the S3 bucket from which the data asset was created"}) + external: Optional[bool] = field(default=None, metadata={"description": "Indicates if the data asset is stored externally"}) @dataclass_json @dataclass(frozen=True) class AppParameter: - name: Optional[str] = None - value: Optional[str] = None + """Name and value of app panel parameters used to generate result data assets.""" + name: Optional[str] = field(default=None, metadata={"description": "Parameter name"}) + value: Optional[str] = field(default=None, metadata={"description": "Parameter value"}) @dataclass_json @dataclass(frozen=True) class ResultsInfo: - capsule_id: Optional[str] = None - pipeline_id: Optional[str] = None - version: Optional[int] = None - commit: Optional[str] = None - run_script: Optional[str] = None - data_assets: Optional[list[str]] = None - parameters: Optional[list[Param]] = None - nextflow_profile: Optional[str] = None - processes: Optional[list[PipelineProcess]] = None + """Additional information for data assets created from exported capsule/pipeline results.""" + capsule_id: Optional[str] = field(default=None, metadata={"description": "ID of the capsule that was executed"}) + pipeline_id: Optional[str] = field(default=None, metadata={"description": "ID of the pipeline that was executed"}) + version: Optional[int] = field(default=None, metadata={"description": "Capsule or pipeline release version"}) + commit: Optional[str] = field(default=None, metadata={"description": "Commit hash of capsule/pipeline code at time of execution"}) + run_script: Optional[str] = field(default=None, metadata={"description": "Path to the script that was executed relative to /Capsule folder"}) + data_assets: Optional[list[str]] = field(default=None, metadata={"description": "IDs of data assets used during the run"}) + parameters: Optional[list[Param]] = field(default=None, metadata={"description": "Run parameters used for execution"}) + nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile"}) + processes: Optional[list[PipelineProcess]] = field(default=None, metadata={"description": "Pipeline processes information"}) @dataclass_json @dataclass(frozen=True) class DataAsset: - id: str - created: int - name: str - mount: str - state: DataAssetState - type: DataAssetType - last_used: int - files: Optional[int] = None - size: Optional[int] = None - description: Optional[str] = None - tags: Optional[list[str]] = None - provenance: Optional[Provenance] = None - source_bucket: Optional[SourceBucket] = None - custom_metadata: Optional[dict] = None - app_parameters: Optional[list[AppParameter]] = None - nextflow_profile: Optional[str] = None - contained_data_assets: Optional[list[ContainedDataAsset]] = None - last_transferred: Optional[int] = None - transfer_error: Optional[str] = None - failure_reason: Optional[str] = None + """Represents a Code Ocean data asset with its metadata and properties.""" + id: str = field(metadata={"description": "Unique data asset ID (UUID string)"}) + created: int = field(metadata={"description": "Data asset creation time (seconds since epoch)"}) + name: str = field(metadata={"description": "Name of the data asset"}) + mount: str = field(metadata={"description": "The default mount folder of the data asset"}) + state: DataAssetState = field(metadata={"description": "Data asset creation state (draft, ready, failed)"}) + type: DataAssetType = field(metadata={"description": "Type of the data asset (dataset, result, combined, model)"}) + last_used: int = field(metadata={"description": "Time data asset was last used in seconds from unix epoch"}) + files: Optional[int] = field(default=None, metadata={"description": "Number of files in the data asset"}) + size: Optional[int] = field(default=None, metadata={"description": "Size in bytes of the data asset (parsed to int)"}) + description: Optional[str] = field(default=None, metadata={"description": "Data asset description"}) + tags: Optional[list[str]] = field(default=None, metadata={"description": "Keywords for searching the data asset"}) + provenance: Optional[Provenance] = field(default=None, metadata={"description": "Shows the data asset provenance if type is result; only 'capsule' or 'computation' field will be populated depending on source"}) + source_bucket: Optional[SourceBucket] = field(default=None, metadata={"description": "Information on bucket from which data asset was created"}) + custom_metadata: Optional[dict] = field(default=None, metadata={"description": "Custom metadata fields defined by deployment admin with user-set values"}) + app_parameters: Optional[list[AppParameter]] = field(default=None, metadata={"description": "Name and value of app panel parameters used to generate result data asset"}) + nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile"}) + contained_data_assets: Optional[list[ContainedDataAsset]] = field(default=None, metadata={"description": "List of contained data assets if type is combined"}) + last_transferred: Optional[int] = field(default=None, metadata={"description": "Time data asset's files were last transferred to a different S3 storage location"}) + transfer_error: Optional[str] = field(default=None, metadata={"description": "The error that occurred during the last transfer attempt if it failed"}) + failure_reason: Optional[str] = field(default=None, metadata={"description": "Reason for data asset creation failure if state is failed"}) @dataclass_json @dataclass(frozen=True) class DataAssetUpdateParams: - name: Optional[str] = None - description: Optional[str] = None - tags: Optional[list[str]] = None - mount: Optional[str] = None - custom_metadata: Optional[dict] = None + """Parameters for updating data asset metadata.""" + name: Optional[str] = field(default=None, metadata={"description": "Data asset name"}) + description: Optional[str] = field(default=None, metadata={"description": "Data asset description"}) + tags: Optional[list[str]] = field(default=None, metadata={"description": "Keywords for searching the data asset"}) + mount: Optional[str] = field(default=None, metadata={"description": "Default mount folder of the data asset"}) + custom_metadata: Optional[dict] = field(default=None, metadata={"description": "Custom metadata fields with user-set values"}) @dataclass_json @dataclass(frozen=True) class AWSS3Source: - bucket: str - prefix: Optional[str] = None - keep_on_external_storage: Optional[bool] = None - public: Optional[bool] = None + """AWS S3 source configuration for creating data assets.""" + bucket: str = field(metadata={"description": "The S3 bucket from which the data asset will be created"}) + prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the S3 bucket from which the data asset will be created"}) + keep_on_external_storage: Optional[bool] = field(default=None, metadata={"description": "When true, data asset files will not be copied to Code Ocean"}) + public: Optional[bool] = field(default=None, metadata={"description": "When true, Code Ocean will access the source bucket without credentials"}) @dataclass_json @dataclass(frozen=True) class GCPCloudStorageSource: - bucket: str - client_id: Optional[str] = None - client_secret: Optional[str] = None - prefix: Optional[str] = None + """Google Cloud Platform Cloud Storage source configuration for creating data assets.""" + bucket: str = field(metadata={"description": "The GCP Cloud Storage bucket from which the data asset will be created"}) + client_id: Optional[str] = field(default=None, metadata={"description": "GCP client ID for authentication"}) + client_secret: Optional[str] = field(default=None, metadata={"description": "GCP client secret for authentication"}) + prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the GCP bucket from which the data asset will be created"}) @dataclass_json @dataclass(frozen=True) class ComputationSource: - id: str - path: Optional[str] = None + """Computation source configuration for creating result data assets.""" + id: str = field(metadata={"description": "Computation ID from which to create the data asset"}) + path: Optional[str] = field(default=None, metadata={"description": "Results path within computation (empty captures all result files)"}) @dataclass_json @dataclass(frozen=True) class Source: - aws: Optional[AWSS3Source] = None - gcp: Optional[GCPCloudStorageSource] = None - computation: Optional[ComputationSource] = None + """Source configuration for data asset creation from various origins.""" + aws: Optional[AWSS3Source] = field(default=None, metadata={"description": "AWS S3 source configuration"}) + gcp: Optional[GCPCloudStorageSource] = field(default=None, metadata={"description": "GCP Cloud Storage source configuration"}) + computation: Optional[ComputationSource] = field(default=None, metadata={"description": "Computation source configuration"}) @dataclass_json @dataclass(frozen=True) class AWSS3Target: - bucket: str - prefix: Optional[str] = None + """AWS S3 target configuration for external data asset storage.""" + bucket: str = field(metadata={"description": "The S3 bucket where the data asset will be stored"}) + prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the S3 bucket where the data asset will be placed"}) @dataclass_json @dataclass(frozen=True) class Target: - aws: Optional[AWSS3Target] = None + """Target configuration for external data asset storage.""" + aws: Optional[AWSS3Target] = field(default=None, metadata={"description": "AWS S3 target configuration"}) @dataclass_json @dataclass(frozen=True) class DataAssetParams: - name: str - tags: list[str] - mount: str - description: Optional[str] = None - source: Optional[Source] = None - target: Optional[Target] = None - custom_metadata: Optional[dict] = None - data_asset_ids: Optional[list[str]] = None - results_info: Optional[ResultsInfo] = None + """Complete parameter set for creating data assets with various source types and configurations.""" + name: str = field(metadata={"description": "Data asset name"}) + tags: list[str] = field(metadata={"description": "Keywords applied to the data asset to aid in searching"}) + mount: str = field(metadata={"description": "Data asset default mount folder"}) + description: Optional[str] = field(default=None, metadata={"description": "Data asset description"}) + source: Optional[Source] = field(default=None, metadata={"description": "Source configuration (AWS S3, GCP, or computation)"}) + target: Optional[Target] = field(default=None, metadata={"description": "Target configuration for external storage"}) + custom_metadata: Optional[dict] = field(default=None, metadata={"description": "Custom metadata fields according to admin-defined fields"}) + data_asset_ids: Optional[list[str]] = field(default=None, metadata={"description": "List of data asset IDs for creating combined data assets"}) + results_info: Optional[ResultsInfo] = field(default=None, metadata={"description": "Additional information for data assets from external results"}) @dataclass_json @dataclass(frozen=True) class DataAssetAttachParams: - id: str - mount: Optional[str] = None + """Parameters for attaching data assets to capsules.""" + id: str = field(metadata={"description": "Data asset ID to attach"}) + mount: Optional[str] = field(default=None, metadata={"description": "Mount path for the attached data asset"}) @dataclass_json @dataclass(frozen=True) class DataAssetAttachResults: - id: str - mount_state: Optional[str] = None - job_id: Optional[str] = None - external: Optional[bool] = None - ready: Optional[bool] = None - mount: Optional[str] = None + """Results from attaching data assets to capsules.""" + id: str = field(metadata={"description": "Data asset ID that was attached"}) + mount_state: Optional[str] = field(default=None, metadata={"description": "Current state of the data asset mount"}) + job_id: Optional[str] = field(default=None, metadata={"description": "Job ID for the attachment operation"}) + external: Optional[bool] = field(default=None, metadata={"description": "Indicates if the data asset is external"}) + ready: Optional[bool] = field(default=None, metadata={"description": "Indicates if the data asset is ready for use"}) + mount: Optional[str] = field(default=None, metadata={"description": "Actual mount path used for the data asset"}) class DataAssetSortBy(StrEnum): + """Fields available for sorting data asset search results.""" Created = "created" Type = "type" Name = "name" @@ -193,6 +212,7 @@ class DataAssetSortBy(StrEnum): class DataAssetSearchOrigin(StrEnum): + """Origin filter for data asset searches.""" Internal = "internal" External = "external" @@ -200,59 +220,89 @@ class DataAssetSearchOrigin(StrEnum): @dataclass_json @dataclass(frozen=True) class DataAssetSearchParams: - query: Optional[str] = None - next_token: Optional[str] = None - offset: Optional[int] = None - limit: Optional[int] = None - sort_field: Optional[DataAssetSortBy] = None - sort_order: Optional[SortOrder] = None - type: Optional[DataAssetType] = None - ownership: Optional[Ownership] = None - origin: Optional[DataAssetSearchOrigin] = None - favorite: Optional[bool] = None - archived: Optional[bool] = None - filters: Optional[list[SearchFilter]] = None + """Parameters for searching data assets with filtering, sorting, and pagination options.""" + query: Optional[str] = field(default=None, metadata={"description": "Search query in free text or structured format (name:... tag:... run_script:... commit_id:...)"}) + next_token: Optional[str] = field(default=None, metadata={"description": "Token for next page of results from previous response"}) + offset: Optional[int] = field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) + limit: Optional[int] = field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) + sort_field: Optional[DataAssetSortBy] = field(default=None, metadata={"description": "Field to sort by (created, type, name, size)"}) + sort_order: Optional[SortOrder] = field(default=None, metadata={"description": "Sort order (asc or desc) - must be provided with sort_field"}) + type: Optional[DataAssetType] = field(default=None, metadata={"description": "Filter by data asset type (dataset, result, combined, model)"}) + ownership: Optional[Ownership] = field(default=None, metadata={"description": "Filter by ownership (created or shared) - defaults to all accessible"}) + origin: Optional[DataAssetSearchOrigin] = field(default=None, metadata={"description": "Filter by origin (internal or external)"}) + favorite: Optional[bool] = field(default=None, metadata={"description": "Search only favorite data assets"}) + archived: Optional[bool] = field(default=None, metadata={"description": "Search only archived data assets"}) + filters: Optional[list[SearchFilter]] = field(default=None, metadata={"description": "Additional field-level filters for name, description, tags, or custom fields"}) @dataclass_json @dataclass(frozen=True) class DataAssetSearchResults: - has_more: bool - results: list[DataAsset] - next_token: Optional[str] = None + """Results from a data asset search operation with pagination support.""" + has_more: bool = field(metadata={"description": "Indicates if there are more results available"}) + results: list[DataAsset] = field(metadata={"description": "Array of data assets found matching the search criteria"}) + next_token: Optional[str] = field(default=None, metadata={"description": "Token for fetching the next page of results"}) @dataclass_json @dataclass(frozen=True) class ContainedDataAsset: - id: Optional[str] = None - mount: Optional[str] = None - size: Optional[int] = None + """Information about data assets contained within a combined data asset.""" + id: Optional[str] = field(default=None, metadata={"description": "ID of the contained data asset"}) + mount: Optional[str] = field(default=None, metadata={"description": "Mount path of the contained data asset"}) + size: Optional[int] = field(default=None, metadata={"description": "Size in bytes of the contained data asset"}) @dataclass_json @dataclass(frozen=True) class TransferDataParams: - target: Target - force: Optional[bool] = None + """Parameters for transferring data asset files to different S3 storage locations.""" + target: Target = field(metadata={"description": "Target storage location configuration"}) + force: Optional[bool] = field(default=None, metadata={"description": "Perform transfer even if there are release pipelines using the data asset"}) @dataclass class DataAssets: + """Client for interacting with Code Ocean data asset APIs.""" client: BaseUrlSession def get_data_asset(self, data_asset_id: str) -> DataAsset: + """Retrieve metadata for a specific data asset by its ID.""" res = self.client.get(f"data_assets/{data_asset_id}") return DataAsset.from_dict(res.json()) def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset: + """ + Update metadata for a data asset including name, description, tags, mount, and custom metadata. + + Supports updating various metadata types: + - Basic metadata: name (display name), description (free text description) + - Organization: tags (keywords for searching), mount (default mount folder path) + - Custom metadata: admin-defined custom fields with user-set values according to + deployment configuration (string, number, or date fields in unix epoch format) + """ res = self.client.put(f"data_assets/{data_asset_id}", json=update_params.to_dict()) return DataAsset.from_dict(res.json()) def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset: + """ + Create a new data asset from various sources including S3 buckets, computation results, or combined assets. + + Data assets are versioned, immutable collections of files that serve as inputs or outputs + for computational workflows in Code Ocean. Internal data assets store files within Code Ocean's + infrastructure, while external data assets reference files in external storage (S3/GCP) without copying. + + Supports creating data assets from AWS S3, GCP Cloud Storage, computation results, or combining + existing data assets. Returns confirmation of creation request validity, not success, as creation + takes time. Use wait_until_ready() to monitor creation progress. + + Typical workflow: 1) create_data_asset() to initiate creation, 2) wait_until_ready() to + monitor progress until state is 'ready', 3) get_data_asset() to fetch final metadata, + 4) list_data_asset_files() and get_data_asset_file_download_url() to access contents. + """ res = self.client.post("data_assets", json=data_asset_params.to_dict()) return DataAsset.from_dict(res.json()) @@ -264,9 +314,19 @@ def wait_until_ready( timeout: float | None = None, ) -> DataAsset: """ - Polls the given data asset until it reaches the 'Ready' or 'Failed' state. - - - `polling_interval` and `timeout` are in seconds + Poll a data asset until it reaches 'Ready' or 'Failed' state with configurable timing. + + Args: + data_asset: The data asset object to monitor + polling_interval: Time between status checks in seconds (minimum 5 seconds) + timeout: Maximum time to wait in seconds, or None for no timeout + + Returns: + Updated data asset object once ready or failed + + Raises: + ValueError: If polling_interval < 5 or timeout constraints are violated + TimeoutError: If data asset doesn't become ready within the timeout period """ if polling_interval < 5: raise ValueError( @@ -293,26 +353,31 @@ def wait_until_ready( sleep(polling_interval) def delete_data_asset(self, data_asset_id: str): + """Delete a data asset permanently.""" self.client.delete(f"data_assets/{data_asset_id}") def update_permissions(self, data_asset_id: str, permissions: Permissions): + """Update permissions for a data asset to control user and group access.""" self.client.post( f"data_assets/{data_asset_id}/permissions", json=permissions.to_dict(), ) def archive_data_asset(self, data_asset_id: str, archive: bool): + """Archive or unarchive a data asset to control its visibility and accessibility.""" self.client.patch( f"data_assets/{data_asset_id}/archive", params={"archive": archive}, ) def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults: + """Search for data assets with filtering, sorting, and pagination options.""" res = self.client.post("data_assets/search", json=search_params.to_dict()) return DataAssetSearchResults.from_dict(res.json()) def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> Iterator[DataAsset]: + """Iterate through all data assets matching search criteria with automatic pagination.""" params = search_params.to_dict() while True: response = self.search_data_assets( @@ -328,6 +393,7 @@ def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> I params["next_token"] = response.next_token def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder: + """List files and folders within an internal data asset at the specified path. Empty path retrieves root level contents.""" data = { "path": path, } @@ -337,6 +403,7 @@ def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder: return Folder.from_dict(res.json()) def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> DownloadFileURL: + """Generate a download URL for a specific file from an internal data asset.""" res = self.client.get( f"data_assets/{data_asset_id}/files/download_url", params={"path": path}, @@ -345,6 +412,13 @@ def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> Dow return DownloadFileURL.from_dict(res.json()) def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataParams): + """ + Transfer a data asset's files to a different S3 storage location (Admin only). + + Can convert internal data assets to external or change storage location of external + data assets. Maintains provenance for result data assets. Use force=True when + transferring data assets used by release pipelines. + """ self.client.post( f"data_assets/{data_asset_id}/transfer", json=transfer_params.to_dict() From 9e833d03677ad5f070ab5e0bfc9d9728832f70e9 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 09:43:23 +0300 Subject: [PATCH 04/19] refactor: enhance metadata descriptions for FolderItem, Folder, ListFolderParams, and DownloadFileURL classes --- src/codeocean/folder.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/codeocean/folder.py b/src/codeocean/folder.py index 43cac89..ea03185 100644 --- a/src/codeocean/folder.py +++ b/src/codeocean/folder.py @@ -1,32 +1,36 @@ from __future__ import annotations from dataclasses_json import dataclass_json -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional @dataclass_json @dataclass(frozen=True) class FolderItem: - name: str - path: str - type: str - size: Optional[int] = None + """Represents a file or folder item within a folder listing.""" + name: str = field(metadata={"description": "Name of the file or folder"}) + path: str = field(metadata={"description": "Path of the file or folder"}) + type: str = field(metadata={"description": "Item type ('file' or 'folder')"}) + size: Optional[int] = field(default=None, metadata={"description": "Size in bytes (only for files)"}) @dataclass_json @dataclass(frozen=True) class Folder: - items: list[FolderItem] + """Represents a folder with its list of items (files and subfolders).""" + items: list[FolderItem] = field(metadata={"description": "List of items in the folder (files and subfolders)"}) @dataclass_json @dataclass(frozen=True) class ListFolderParams: - path: str + """Parameters for listing contents of a folder.""" + path: str = field(metadata={"description": "Path of the folder to list; empty string for root"}) @dataclass_json @dataclass(frozen=True) class DownloadFileURL: - url: str + """Download URL information for retrieving a file.""" + url: str = field(metadata={"description": "Pre-signed URL for downloading the specified file"}) From 8115b86d77cce30e05eba4c7d0c4c895c3062430 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 09:45:12 +0300 Subject: [PATCH 05/19] refactor: enhance metadata descriptions for user, group, and permission classes --- src/codeocean/components.py | 42 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/codeocean/components.py b/src/codeocean/components.py index de80325..652df0b 100644 --- a/src/codeocean/components.py +++ b/src/codeocean/components.py @@ -1,13 +1,14 @@ from __future__ import annotations from dataclasses_json import dataclass_json -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional from codeocean.enum import StrEnum class UserRole(StrEnum): + """Role levels for user permissions in Code Ocean resources.""" Owner = "owner" Editor = "editor" Viewer = "viewer" @@ -16,11 +17,13 @@ class UserRole(StrEnum): @dataclass_json @dataclass(frozen=True) class UserPermissions: - email: str - role: UserRole + """User permission configuration with email and role assignment.""" + email: str = field(metadata={"description": "User email address for permission assignment"}) + role: UserRole = field(metadata={"description": "Permission level granted to the user (owner, editor, viewer)"}) class GroupRole(StrEnum): + """Role levels for group permissions in Code Ocean resources.""" Owner = "owner" Editor = "editor" Viewer = "viewer" @@ -30,11 +33,13 @@ class GroupRole(StrEnum): @dataclass_json @dataclass(frozen=True) class GroupPermissions: - group: str - role: GroupRole + """Group permission configuration with group identifier and role assignment.""" + group: str = field(metadata={"description": "Group identifier for permission assignment"}) + role: GroupRole = field(metadata={"description": "Permission level granted to the group (owner, editor, viewer, discoverable)"}) class EveryoneRole(StrEnum): + """Role levels for public access permissions in Code Ocean resources.""" Viewer = "viewer" Discoverable = "discoverable" None_ = "none" @@ -43,13 +48,15 @@ class EveryoneRole(StrEnum): @dataclass_json @dataclass(frozen=True) class Permissions: - users: Optional[list[UserPermissions]] = None - groups: Optional[list[GroupPermissions]] = None - everyone: Optional[EveryoneRole] = None - share_assets: Optional[bool] = None + """Complete permission configuration for Code Ocean resources including users, groups, and public access.""" + users: Optional[list[UserPermissions]] = field(default=None, metadata={"description": "List of user-specific permissions"}) + groups: Optional[list[GroupPermissions]] = field(default=None, metadata={"description": "List of group-specific permissions"}) + everyone: Optional[EveryoneRole] = field(default=None, metadata={"description": "Public access level (viewer, discoverable, none)"}) + share_assets: Optional[bool] = field(default=None, metadata={"description": "Whether to share associated assets with granted permissions"}) class SortOrder(StrEnum): + """Sort order options for search and listing operations.""" Ascending = "asc" Descending = "desc" @@ -57,21 +64,24 @@ class SortOrder(StrEnum): @dataclass_json @dataclass(frozen=True) class SearchFilterRange: - min: float - max: float + """Numeric range filter for search operations with minimum and maximum values.""" + min: float = field(metadata={"description": "Minimum value for range filter"}) + max: float = field(metadata={"description": "Maximum value for range filter"}) @dataclass_json @dataclass(frozen=True) class SearchFilter: - key: str - value: Optional[str | float] = None - values: Optional[list[str | float]] = None - range: Optional[SearchFilterRange] = None - exclude: Optional[bool] = None + """Search filter configuration for field-level filtering with various value types and range support.""" + key: str = field(metadata={"description": "Field name to filter on (name, description, tags, or custom field key)"}) + value: Optional[str | float] = field(default=None, metadata={"description": "Single field value to include/exclude"}) + values: Optional[list[str | float]] = field(default=None, metadata={"description": "Multiple field values for inclusion/exclusion"}) + range: Optional[SearchFilterRange] = field(default=None, metadata={"description": "Numeric range filter (only one of min/max must be set)"}) + exclude: Optional[bool] = field(default=None, metadata={"description": "Whether to include (false) or exclude (true) the specified values"}) class Ownership(StrEnum): + """Ownership filter options for search operations.""" Private = "private" Shared = "shared" Created = "created" From 54c7c9f7d6557609109979b98236ae8bc6cf6e45 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 10:03:12 +0300 Subject: [PATCH 06/19] refactor: standardize field imports in Capsule and related classes Preserves the required attribute name - You can keep field as needed Eliminates the runtime error - No more TypeError --- src/codeocean/capsule.py | 72 ++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index 8b2e7af..a48c44b 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass, field as dataclass_field from dataclasses_json import dataclass_json from typing import Optional, Iterator from requests_toolbelt.sessions import BaseUrlSession @@ -28,59 +28,59 @@ class CapsuleSortBy(StrEnum): @dataclass(frozen=True) class OriginalCapsuleInfo: """Information about the original capsule when this capsule is cloned from another.""" - id: Optional[str] = field(default=None, metadata={"description": "Original capsule ID"}) - major_version: Optional[int] = field(default=None, metadata={"description": "Original capsule major version"}) - minor_version: Optional[int] = field(default=None, metadata={"description": "Original capsule minor version"}) - name: Optional[str] = field(default=None, metadata={"description": "Original capsule name"}) - created: Optional[int] = field(default=None, metadata={"description": "Original capsule creation time (int64 timestamp)"}) - public: Optional[bool] = field(default=None, metadata={"description": "Indicates whether the original capsule is public"}) + id: Optional[str] = dataclass_field(default=None, metadata={"description": "Original capsule ID"}) + major_version: Optional[int] = dataclass_field(default=None, metadata={"description": "Original capsule major version"}) + minor_version: Optional[int] = dataclass_field(default=None, metadata={"description": "Original capsule minor version"}) + name: Optional[str] = dataclass_field(default=None, metadata={"description": "Original capsule name"}) + created: Optional[int] = dataclass_field(default=None, metadata={"description": "Original capsule creation time (int64 timestamp)"}) + public: Optional[bool] = dataclass_field(default=None, metadata={"description": "Indicates whether the original capsule is public"}) @dataclass_json @dataclass(frozen=True) class Capsule: """Represents a Code Ocean capsule with its metadata and properties.""" - id: str = field(metadata={"description": "Capsule ID"}) - created: int = field(metadata={"description": "Capsule creation time (int64 timestamp)"}) - name: str = field(metadata={"description": "Capsule display name"}) - status: CapsuleStatus = field(metadata={"description": "Status of the capsule (non_release or release)"}) - owner: str = field(metadata={"description": "Capsule owner's ID"}) - slug: str = field(metadata={"description": "Alternate capsule ID (URL-friendly identifier)"}) - article: Optional[dict] = field(default=None, metadata={"description": "Capsule article info with URL, ID, DOI, citation, state, name, journal_name, and publish_time"}) - cloned_from_url: Optional[str] = field(default=None, metadata={"description": "URL to external Git repository linked to capsule"}) - description: Optional[str] = field(default=None, metadata={"description": "Capsule description"}) - field: Optional[str] = field(default=None, metadata={"description": "Capsule research field"}) - tags: Optional[list[str]] = field(default=None, metadata={"description": "List of tags associated with the capsule"}) - original_capsule: Optional[OriginalCapsuleInfo] = field(default=None, metadata={"description": "Original capsule info when this is cloned from another capsule"}) - release_capsule: Optional[str] = field(default=None, metadata={"description": "Release capsule ID"}) - submission: Optional[dict] = field(default=None, metadata={"description": "Submission info with timestamp, commit hash, verification_capsule, verified status, and verified_timestamp"}) - versions: Optional[list[dict]] = field(default=None, metadata={"description": "Capsule versions with major_version, minor_version, release_time, and DOI"}) + id: str = dataclass_field(metadata={"description": "Capsule ID"}) + created: int = dataclass_field(metadata={"description": "Capsule creation time (int64 timestamp)"}) + name: str = dataclass_field(metadata={"description": "Capsule display name"}) + status: CapsuleStatus = dataclass_field(metadata={"description": "Status of the capsule (non_release or release)"}) + owner: str = dataclass_field(metadata={"description": "Capsule owner's ID"}) + slug: str = dataclass_field(metadata={"description": "Alternate capsule ID (URL-friendly identifier)"}) + article: Optional[dict] = dataclass_field(default=None, metadata={"description": "Capsule article info with URL, ID, DOI, citation, state, name, journal_name, and publish_time"}) + cloned_from_url: Optional[str] = dataclass_field(default=None, metadata={"description": "URL to external Git repository linked to capsule"}) + description: Optional[str] = dataclass_field(default=None, metadata={"description": "Capsule description"}) + field: Optional[str] = dataclass_field(default=None, metadata={"description": "Capsule research field"}) + tags: Optional[list[str]] = dataclass_field(default=None, metadata={"description": "List of tags associated with the capsule"}) + original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field(default=None, metadata={"description": "Original capsule info when this is cloned from another capsule"}) + release_capsule: Optional[str] = dataclass_field(default=None, metadata={"description": "Release capsule ID"}) + submission: Optional[dict] = dataclass_field(default=None, metadata={"description": "Submission info with timestamp, commit hash, verification_capsule, verified status, and verified_timestamp"}) + versions: Optional[list[dict]] = dataclass_field(default=None, metadata={"description": "Capsule versions with major_version, minor_version, release_time, and DOI"}) @dataclass_json @dataclass(frozen=True) class CapsuleSearchParams: """Parameters for searching capsules with various filters and pagination options.""" - query: Optional[str] = field(default=None, metadata={"description": "Search query in free text or structured format (name:... tag:...)"}) - next_token: Optional[str] = field(default=None, metadata={"description": "Token for next page of results from previous response"}) - offset: Optional[int] = field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) - limit: Optional[int] = field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) - sort_field: Optional[CapsuleSortBy] = field(default=None, metadata={"description": "Field to sort by (created, name, last_accessed)"}) - sort_order: Optional[SortOrder] = field(default=None, metadata={"description": "Sort order (asc or desc) - must be provided with sort_field"}) - ownership: Optional[Ownership] = field(default=None, metadata={"description": "Filter by ownership (created or shared) - defaults to all accessible"}) - status: Optional[CapsuleStatus] = field(default=None, metadata={"description": "Filter by status (release or non_release) - defaults to all"}) - favorite: Optional[bool] = field(default=None, metadata={"description": "Search only favorite capsules"}) - archived: Optional[bool] = field(default=None, metadata={"description": "Search only archived capsules"}) - filters: Optional[list[SearchFilter]] = field(default=None, metadata={"description": "Additional field-level filters for name, description, tags, or custom fields"}) + query: Optional[str] = dataclass_field(default=None, metadata={"description": "Search query in free text or structured format (name:... tag:...)"}) + next_token: Optional[str] = dataclass_field(default=None, metadata={"description": "Token for next page of results from previous response"}) + offset: Optional[int] = dataclass_field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) + limit: Optional[int] = dataclass_field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) + sort_field: Optional[CapsuleSortBy] = dataclass_field(default=None, metadata={"description": "Field to sort by (created, name, last_accessed)"}) + sort_order: Optional[SortOrder] = dataclass_field(default=None, metadata={"description": "Sort order (asc or desc) - must be provided with sort_field"}) + ownership: Optional[Ownership] = dataclass_field(default=None, metadata={"description": "Filter by ownership (created or shared) - defaults to all accessible"}) + status: Optional[CapsuleStatus] = dataclass_field(default=None, metadata={"description": "Filter by status (release or non_release) - defaults to all"}) + favorite: Optional[bool] = dataclass_field(default=None, metadata={"description": "Search only favorite capsules"}) + archived: Optional[bool] = dataclass_field(default=None, metadata={"description": "Search only archived capsules"}) + filters: Optional[list[SearchFilter]] = dataclass_field(default=None, metadata={"description": "Additional field-level filters for name, description, tags, or custom fields"}) @dataclass_json @dataclass(frozen=True) class CapsuleSearchResults: """Results from a capsule search operation with pagination support.""" - has_more: bool = field(metadata={"description": "Indicates if there are more results available"}) - results: list[Capsule] = field(metadata={"description": "Array of capsules found matching the search criteria"}) - next_token: Optional[str] = field(default=None, metadata={"description": "Token for fetching the next page of results"}) + has_more: bool = dataclass_field(metadata={"description": "Indicates if there are more results available"}) + results: list[Capsule] = dataclass_field(metadata={"description": "Array of capsules found matching the search criteria"}) + next_token: Optional[str] = dataclass_field(default=None, metadata={"description": "Token for fetching the next page of results"}) @dataclass From 989a3c4ce39f6e82ea9777fd11575cb79b7c0c77 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 12:31:17 +0300 Subject: [PATCH 07/19] refactor: enhance metadata descriptions for CapsuleSearchParams class --- src/codeocean/capsule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index a48c44b..8aab378 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -66,8 +66,8 @@ class CapsuleSearchParams: offset: Optional[int] = dataclass_field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) limit: Optional[int] = dataclass_field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) sort_field: Optional[CapsuleSortBy] = dataclass_field(default=None, metadata={"description": "Field to sort by (created, name, last_accessed)"}) - sort_order: Optional[SortOrder] = dataclass_field(default=None, metadata={"description": "Sort order (asc or desc) - must be provided with sort_field"}) - ownership: Optional[Ownership] = dataclass_field(default=None, metadata={"description": "Filter by ownership (created or shared) - defaults to all accessible"}) + sort_order: Optional[SortOrder] = dataclass_field(default=None, metadata={"description": "Sort order ('asc' or 'desc') - must be provided with a sort_field parameter as well!"}) + ownership: Optional[Ownership] = dataclass_field(default=None, metadata={"description": "Filter by ownership ('private', 'created' or 'shared') - defaults to all accessible"}) status: Optional[CapsuleStatus] = dataclass_field(default=None, metadata={"description": "Filter by status (release or non_release) - defaults to all"}) favorite: Optional[bool] = dataclass_field(default=None, metadata={"description": "Search only favorite capsules"}) archived: Optional[bool] = dataclass_field(default=None, metadata={"description": "Search only archived capsules"}) From 85bd0a2d86680fb3ad6c7ae6a18f2984c57ef38e Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 12:59:23 +0300 Subject: [PATCH 08/19] linting issues --- src/codeocean/capsule.py | 176 ++++++++++++++++++++++------ src/codeocean/client.py | 1 - src/codeocean/components.py | 79 ++++++++++--- src/codeocean/computation.py | 215 +++++++++++++++++++++++++++-------- src/codeocean/folder.py | 20 +++- 5 files changed, 391 insertions(+), 100 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index 8aab378..ea2eb69 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -13,12 +13,14 @@ class CapsuleStatus(StrEnum): """Status of a capsule indicating its release state.""" + NonRelease = "non_release" Release = "release" class CapsuleSortBy(StrEnum): """Fields available for sorting capsule search results.""" + Created = "created" LastAccessed = "last_accessed" Name = "name" @@ -28,59 +30,169 @@ class CapsuleSortBy(StrEnum): @dataclass(frozen=True) class OriginalCapsuleInfo: """Information about the original capsule when this capsule is cloned from another.""" - id: Optional[str] = dataclass_field(default=None, metadata={"description": "Original capsule ID"}) - major_version: Optional[int] = dataclass_field(default=None, metadata={"description": "Original capsule major version"}) - minor_version: Optional[int] = dataclass_field(default=None, metadata={"description": "Original capsule minor version"}) - name: Optional[str] = dataclass_field(default=None, metadata={"description": "Original capsule name"}) - created: Optional[int] = dataclass_field(default=None, metadata={"description": "Original capsule creation time (int64 timestamp)"}) - public: Optional[bool] = dataclass_field(default=None, metadata={"description": "Indicates whether the original capsule is public"}) + + id: Optional[str] = dataclass_field( + default=None, metadata={"description": "Original capsule ID"} + ) + major_version: Optional[int] = dataclass_field( + default=None, metadata={"description": "Original capsule major version"} + ) + minor_version: Optional[int] = dataclass_field( + default=None, metadata={"description": "Original capsule minor version"} + ) + name: Optional[str] = dataclass_field( + default=None, metadata={"description": "Original capsule name"} + ) + created: Optional[int] = dataclass_field( + default=None, + metadata={"description": "Original capsule creation time (int64 timestamp)"}, + ) + public: Optional[bool] = dataclass_field( + default=None, + metadata={"description": "Indicates whether the original capsule is public"}, + ) @dataclass_json @dataclass(frozen=True) class Capsule: """Represents a Code Ocean capsule with its metadata and properties.""" + id: str = dataclass_field(metadata={"description": "Capsule ID"}) - created: int = dataclass_field(metadata={"description": "Capsule creation time (int64 timestamp)"}) + created: int = dataclass_field( + metadata={"description": "Capsule creation time (int64 timestamp)"} + ) name: str = dataclass_field(metadata={"description": "Capsule display name"}) - status: CapsuleStatus = dataclass_field(metadata={"description": "Status of the capsule (non_release or release)"}) + status: CapsuleStatus = dataclass_field( + metadata={"description": "Status of the capsule (non_release or release)"} + ) owner: str = dataclass_field(metadata={"description": "Capsule owner's ID"}) - slug: str = dataclass_field(metadata={"description": "Alternate capsule ID (URL-friendly identifier)"}) - article: Optional[dict] = dataclass_field(default=None, metadata={"description": "Capsule article info with URL, ID, DOI, citation, state, name, journal_name, and publish_time"}) - cloned_from_url: Optional[str] = dataclass_field(default=None, metadata={"description": "URL to external Git repository linked to capsule"}) - description: Optional[str] = dataclass_field(default=None, metadata={"description": "Capsule description"}) - field: Optional[str] = dataclass_field(default=None, metadata={"description": "Capsule research field"}) - tags: Optional[list[str]] = dataclass_field(default=None, metadata={"description": "List of tags associated with the capsule"}) - original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field(default=None, metadata={"description": "Original capsule info when this is cloned from another capsule"}) - release_capsule: Optional[str] = dataclass_field(default=None, metadata={"description": "Release capsule ID"}) - submission: Optional[dict] = dataclass_field(default=None, metadata={"description": "Submission info with timestamp, commit hash, verification_capsule, verified status, and verified_timestamp"}) - versions: Optional[list[dict]] = dataclass_field(default=None, metadata={"description": "Capsule versions with major_version, minor_version, release_time, and DOI"}) + slug: str = dataclass_field( + metadata={"description": "Alternate capsule ID (URL-friendly identifier)"} + ) + article: Optional[dict] = dataclass_field( + default=None, + metadata={ + "description": "Capsule article info with URL, ID, DOI, citation, state, name, journal_name, and publish_time" + }, + ) + cloned_from_url: Optional[str] = dataclass_field( + default=None, + metadata={"description": "URL to external Git repository linked to capsule"}, + ) + description: Optional[str] = dataclass_field( + default=None, metadata={"description": "Capsule description"} + ) + field: Optional[str] = dataclass_field( + default=None, metadata={"description": "Capsule research field"} + ) + tags: Optional[list[str]] = dataclass_field( + default=None, + metadata={"description": "List of tags associated with the capsule"}, + ) + original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field( + default=None, + metadata={ + "description": "Original capsule info when this is cloned from another capsule" + }, + ) + release_capsule: Optional[str] = dataclass_field( + default=None, metadata={"description": "Release capsule ID"} + ) + submission: Optional[dict] = dataclass_field( + default=None, + metadata={ + "description": "Submission info with timestamp, commit hash, verification_capsule, verified status, and verified_timestamp" + }, + ) + versions: Optional[list[dict]] = dataclass_field( + default=None, + metadata={ + "description": "Capsule versions with major_version, minor_version, release_time, and DOI" + }, + ) @dataclass_json @dataclass(frozen=True) class CapsuleSearchParams: """Parameters for searching capsules with various filters and pagination options.""" - query: Optional[str] = dataclass_field(default=None, metadata={"description": "Search query in free text or structured format (name:... tag:...)"}) - next_token: Optional[str] = dataclass_field(default=None, metadata={"description": "Token for next page of results from previous response"}) - offset: Optional[int] = dataclass_field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) - limit: Optional[int] = dataclass_field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) - sort_field: Optional[CapsuleSortBy] = dataclass_field(default=None, metadata={"description": "Field to sort by (created, name, last_accessed)"}) - sort_order: Optional[SortOrder] = dataclass_field(default=None, metadata={"description": "Sort order ('asc' or 'desc') - must be provided with a sort_field parameter as well!"}) - ownership: Optional[Ownership] = dataclass_field(default=None, metadata={"description": "Filter by ownership ('private', 'created' or 'shared') - defaults to all accessible"}) - status: Optional[CapsuleStatus] = dataclass_field(default=None, metadata={"description": "Filter by status (release or non_release) - defaults to all"}) - favorite: Optional[bool] = dataclass_field(default=None, metadata={"description": "Search only favorite capsules"}) - archived: Optional[bool] = dataclass_field(default=None, metadata={"description": "Search only archived capsules"}) - filters: Optional[list[SearchFilter]] = dataclass_field(default=None, metadata={"description": "Additional field-level filters for name, description, tags, or custom fields"}) + + query: Optional[str] = dataclass_field( + default=None, + metadata={ + "description": "Search query in free text or structured format (name:... tag:...)" + }, + ) + next_token: Optional[str] = dataclass_field( + default=None, + metadata={ + "description": "Token for next page of results from previous response" + }, + ) + offset: Optional[int] = dataclass_field( + default=None, + metadata={ + "description": "Starting index for search results (ignored if next_token is set)" + }, + ) + limit: Optional[int] = dataclass_field( + default=None, + metadata={ + "description": "Number of items to return (up to 1000, defaults to 100)" + }, + ) + sort_field: Optional[CapsuleSortBy] = dataclass_field( + default=None, + metadata={"description": "Field to sort by (created, name, last_accessed)"}, + ) + sort_order: Optional[SortOrder] = dataclass_field( + default=None, + metadata={ + "description": "Sort order ('asc' or 'desc') - must be provided with a sort_field parameter as well!" + }, + ) + ownership: Optional[Ownership] = dataclass_field( + default=None, + metadata={ + "description": "Filter by ownership ('private', 'created' or 'shared') - defaults to all accessible" + }, + ) + status: Optional[CapsuleStatus] = dataclass_field( + default=None, + metadata={ + "description": "Filter by status (release or non_release) - defaults to all" + }, + ) + favorite: Optional[bool] = dataclass_field( + default=None, metadata={"description": "Search only favorite capsules"} + ) + archived: Optional[bool] = dataclass_field( + default=None, metadata={"description": "Search only archived capsules"} + ) + filters: Optional[list[SearchFilter]] = dataclass_field( + default=None, + metadata={ + "description": "Additional field-level filters for name, description, tags, or custom fields" + }, + ) @dataclass_json @dataclass(frozen=True) class CapsuleSearchResults: """Results from a capsule search operation with pagination support.""" - has_more: bool = dataclass_field(metadata={"description": "Indicates if there are more results available"}) - results: list[Capsule] = dataclass_field(metadata={"description": "Array of capsules found matching the search criteria"}) - next_token: Optional[str] = dataclass_field(default=None, metadata={"description": "Token for fetching the next page of results"}) + + has_more: bool = dataclass_field( + metadata={"description": "Indicates if there are more results available"} + ) + results: list[Capsule] = dataclass_field( + metadata={"description": "Array of capsules found matching the search criteria"} + ) + next_token: Optional[str] = dataclass_field( + default=None, + metadata={"description": "Token for fetching the next page of results"}, + ) @dataclass diff --git a/src/codeocean/client.py b/src/codeocean/client.py index ef15638..73ad68c 100644 --- a/src/codeocean/client.py +++ b/src/codeocean/client.py @@ -13,7 +13,6 @@ @dataclass class CodeOcean: - domain: str token: str retries: Optional[Retry | int] = 0 diff --git a/src/codeocean/components.py b/src/codeocean/components.py index 652df0b..f3c02f5 100644 --- a/src/codeocean/components.py +++ b/src/codeocean/components.py @@ -9,6 +9,7 @@ class UserRole(StrEnum): """Role levels for user permissions in Code Ocean resources.""" + Owner = "owner" Editor = "editor" Viewer = "viewer" @@ -18,12 +19,20 @@ class UserRole(StrEnum): @dataclass(frozen=True) class UserPermissions: """User permission configuration with email and role assignment.""" - email: str = field(metadata={"description": "User email address for permission assignment"}) - role: UserRole = field(metadata={"description": "Permission level granted to the user (owner, editor, viewer)"}) + + email: str = field( + metadata={"description": "User email address for permission assignment"} + ) + role: UserRole = field( + metadata={ + "description": "Permission level granted to the user (owner, editor, viewer)" + } + ) class GroupRole(StrEnum): """Role levels for group permissions in Code Ocean resources.""" + Owner = "owner" Editor = "editor" Viewer = "viewer" @@ -34,12 +43,20 @@ class GroupRole(StrEnum): @dataclass(frozen=True) class GroupPermissions: """Group permission configuration with group identifier and role assignment.""" - group: str = field(metadata={"description": "Group identifier for permission assignment"}) - role: GroupRole = field(metadata={"description": "Permission level granted to the group (owner, editor, viewer, discoverable)"}) + + group: str = field( + metadata={"description": "Group identifier for permission assignment"} + ) + role: GroupRole = field( + metadata={ + "description": "Permission level granted to the group (owner, editor, viewer, discoverable)" + } + ) class EveryoneRole(StrEnum): """Role levels for public access permissions in Code Ocean resources.""" + Viewer = "viewer" Discoverable = "discoverable" None_ = "none" @@ -49,14 +66,28 @@ class EveryoneRole(StrEnum): @dataclass(frozen=True) class Permissions: """Complete permission configuration for Code Ocean resources including users, groups, and public access.""" - users: Optional[list[UserPermissions]] = field(default=None, metadata={"description": "List of user-specific permissions"}) - groups: Optional[list[GroupPermissions]] = field(default=None, metadata={"description": "List of group-specific permissions"}) - everyone: Optional[EveryoneRole] = field(default=None, metadata={"description": "Public access level (viewer, discoverable, none)"}) - share_assets: Optional[bool] = field(default=None, metadata={"description": "Whether to share associated assets with granted permissions"}) + + users: Optional[list[UserPermissions]] = field( + default=None, metadata={"description": "List of user-specific permissions"} + ) + groups: Optional[list[GroupPermissions]] = field( + default=None, metadata={"description": "List of group-specific permissions"} + ) + everyone: Optional[EveryoneRole] = field( + default=None, + metadata={"description": "Public access level (viewer, discoverable, none)"}, + ) + share_assets: Optional[bool] = field( + default=None, + metadata={ + "description": "Whether to share associated assets with granted permissions" + }, + ) class SortOrder(StrEnum): """Sort order options for search and listing operations.""" + Ascending = "asc" Descending = "desc" @@ -65,6 +96,7 @@ class SortOrder(StrEnum): @dataclass(frozen=True) class SearchFilterRange: """Numeric range filter for search operations with minimum and maximum values.""" + min: float = field(metadata={"description": "Minimum value for range filter"}) max: float = field(metadata={"description": "Maximum value for range filter"}) @@ -73,15 +105,36 @@ class SearchFilterRange: @dataclass(frozen=True) class SearchFilter: """Search filter configuration for field-level filtering with various value types and range support.""" - key: str = field(metadata={"description": "Field name to filter on (name, description, tags, or custom field key)"}) - value: Optional[str | float] = field(default=None, metadata={"description": "Single field value to include/exclude"}) - values: Optional[list[str | float]] = field(default=None, metadata={"description": "Multiple field values for inclusion/exclusion"}) - range: Optional[SearchFilterRange] = field(default=None, metadata={"description": "Numeric range filter (only one of min/max must be set)"}) - exclude: Optional[bool] = field(default=None, metadata={"description": "Whether to include (false) or exclude (true) the specified values"}) + + key: str = field( + metadata={ + "description": "Field name to filter on (name, description, tags, or custom field key)" + } + ) + value: Optional[str | float] = field( + default=None, metadata={"description": "Single field value to include/exclude"} + ) + values: Optional[list[str | float]] = field( + default=None, + metadata={"description": "Multiple field values for inclusion/exclusion"}, + ) + range: Optional[SearchFilterRange] = field( + default=None, + metadata={ + "description": "Numeric range filter (only one of min/max must be set)" + }, + ) + exclude: Optional[bool] = field( + default=None, + metadata={ + "description": "Whether to include (false) or exclude (true) the specified values" + }, + ) class Ownership(StrEnum): """Ownership filter options for search operations.""" + Private = "private" Shared = "shared" Created = "created" diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index faf2aed..d6c237a 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -12,6 +12,7 @@ class ComputationState(StrEnum): """Current state of a computation during its execution lifecycle.""" + Initializing = "initializing" Running = "running" Finalizing = "finalizing" @@ -21,6 +22,7 @@ class ComputationState(StrEnum): class ComputationEndStatus(StrEnum): """Final status of a computation once it has completed execution.""" + Succeeded = "succeeded" Failed = "failed" Stopped = "stopped" @@ -30,63 +32,137 @@ class ComputationEndStatus(StrEnum): @dataclass(frozen=True) class Param: """Parameter information for computations with name and value.""" - name: Optional[str] = field(default=None, metadata={"description": "Parameter label/display name"}) - param_name: Optional[str] = field(default=None, metadata={"description": "Internal parameter name identifier"}) - value: Optional[str] = field(default=None, metadata={"description": "Parameter value as string"}) + + name: Optional[str] = field( + default=None, metadata={"description": "Parameter label/display name"} + ) + param_name: Optional[str] = field( + default=None, metadata={"description": "Internal parameter name identifier"} + ) + value: Optional[str] = field( + default=None, metadata={"description": "Parameter value as string"} + ) @dataclass_json @dataclass(frozen=True) class PipelineProcess: """Information about a process within a pipeline execution.""" - name: str = field(metadata={"description": "Pipeline process name as it appears in main.nf"}) - capsule_id: str = field(metadata={"description": "ID of the capsule executed in this process"}) - version: Optional[int] = field(default=None, metadata={"description": "Capsule version if it's a released capsule"}) - public: Optional[bool] = field(default=None, metadata={"description": "Indicates if the capsule is a Code Ocean public app"}) - parameters: Optional[list[Param]] = field(default=None, metadata={"description": "Run parameters for this process"}) + + name: str = field( + metadata={"description": "Pipeline process name as it appears in main.nf"} + ) + capsule_id: str = field( + metadata={"description": "ID of the capsule executed in this process"} + ) + version: Optional[int] = field( + default=None, + metadata={"description": "Capsule version if it's a released capsule"}, + ) + public: Optional[bool] = field( + default=None, + metadata={"description": "Indicates if the capsule is a Code Ocean public app"}, + ) + parameters: Optional[list[Param]] = field( + default=None, metadata={"description": "Run parameters for this process"} + ) @dataclass_json @dataclass(frozen=True) class InputDataAsset: """Data asset attached to a computation with mount information.""" + id: str = field(metadata={"description": "Attached data asset ID"}) - mount: Optional[str] = field(default=None, metadata={"description": "Mount path for the attached data asset"}) + mount: Optional[str] = field( + default=None, metadata={"description": "Mount path for the attached data asset"} + ) @dataclass_json @dataclass(frozen=True) class Computation: """Represents a Code Ocean computation run with its metadata and execution details.""" + id: str = field(metadata={"description": "Unique computation ID"}) - created: int = field(metadata={"description": "Computation creation time (int64 timestamp)"}) + created: int = field( + metadata={"description": "Computation creation time (int64 timestamp)"} + ) name: str = field(metadata={"description": "Display name of the computation"}) run_time: int = field(metadata={"description": "Total run time in seconds"}) - state: ComputationState = field(metadata={"description": "Current state of the computation (initializing, running, finalizing, completed, failed)"}) - cloud_workstation: Optional[bool] = field(default=None, metadata={"description": "Indicates whether this computation is a cloud workstation"}) - data_assets: Optional[list[InputDataAsset]] = field(default=None, metadata={"description": "List of data assets attached to this computation"}) - parameters: Optional[list[Param]] = field(default=None, metadata={"description": "Run parameters used for this computation"}) - nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile used for this computation"}) - processes: Optional[list[PipelineProcess]] = field(default=None, metadata={"description": "Pipeline processes information if this is a pipeline computation"}) - end_status: Optional[ComputationEndStatus] = field(default=None, metadata={"description": "Final status once computation is completed (succeeded, failed, stopped)"}) - exit_code: Optional[int] = field(default=None, metadata={"description": "Exit code (0 for success, non-zero for failure, 1 for pipeline errors)"}) - has_results: Optional[bool] = field(default=None, metadata={"description": "Indicates whether the computation has generated results"}) - nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile used for this computation"}) + state: ComputationState = field( + metadata={ + "description": "Current state of the computation (initializing, running, finalizing, completed, failed)" + } + ) + cloud_workstation: Optional[bool] = field( + default=None, + metadata={ + "description": "Indicates whether this computation is a cloud workstation" + }, + ) + data_assets: Optional[list[InputDataAsset]] = field( + default=None, + metadata={"description": "List of data assets attached to this computation"}, + ) + parameters: Optional[list[Param]] = field( + default=None, + metadata={"description": "Run parameters used for this computation"}, + ) + nextflow_profile: Optional[str] = field( + default=None, + metadata={"description": "Pipeline Nextflow profile used for this computation"}, + ) + processes: Optional[list[PipelineProcess]] = field( + default=None, + metadata={ + "description": "Pipeline processes information if this is a pipeline computation" + }, + ) + end_status: Optional[ComputationEndStatus] = field( + default=None, + metadata={ + "description": "Final status once computation is completed (succeeded, failed, stopped)" + }, + ) + exit_code: Optional[int] = field( + default=None, + metadata={ + "description": "Exit code (0 for success, non-zero for failure, 1 for pipeline errors)" + }, + ) + has_results: Optional[bool] = field( + default=None, + metadata={ + "description": "Indicates whether the computation has generated results" + }, + ) + nextflow_profile: Optional[str] = field( + default=None, + metadata={"description": "Pipeline Nextflow profile used for this computation"}, + ) @dataclass_json @dataclass(frozen=True) class DataAssetsRunParam: """Data asset parameter for running computations with mount specification.""" + id: str = field(metadata={"description": "Data asset ID to attach"}) - mount: Optional[str] = field(default=None, metadata={"description": "Mount path where the data asset will be accessible"}) + mount: Optional[str] = field( + default=None, + metadata={"description": "Mount path where the data asset will be accessible"}, + ) @dataclass_json @dataclass(frozen=True) class NamedRunParam: """Named parameter for running computations with explicit parameter name.""" - param_name: str = field(metadata={"description": "Internal parameter name identifier"}) + + param_name: str = field( + metadata={"description": "Internal parameter name identifier"} + ) value: str = field(metadata={"description": "Parameter value as string"}) @@ -94,25 +170,63 @@ class NamedRunParam: @dataclass(frozen=True) class PipelineProcessParams: """Parameters for configuring a specific process within a pipeline execution.""" - name: str = field(metadata={"description": "Name of the pipeline process to configure"}) - parameters: Optional[list[str]] = field(default=None, metadata={"description": "Ordered list of parameter values for this process"}) - named_parameters: Optional[list[NamedRunParam]] = field(default=None, metadata={"description": "Named parameters for this process"}) + + name: str = field( + metadata={"description": "Name of the pipeline process to configure"} + ) + parameters: Optional[list[str]] = field( + default=None, + metadata={"description": "Ordered list of parameter values for this process"}, + ) + named_parameters: Optional[list[NamedRunParam]] = field( + default=None, metadata={"description": "Named parameters for this process"} + ) @dataclass_json @dataclass(frozen=True) class RunParams: """Complete parameter set for running capsules or pipelines with data assets and configuration.""" - capsule_id: Optional[str] = field(default=None, metadata={"description": "ID of the capsule to run (required for capsule runs)"}) - pipeline_id: Optional[str] = field(default=None, metadata={"description": "ID of the pipeline to run (required for pipeline runs)"}) - version: Optional[int] = field(default=None, metadata={"description": "Specific version of the capsule to run"}) - resume_run_id: Optional[str] = field(default=None, metadata={"description": "ID of a previous computation to resume from"}) - nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Nextflow profile to use when running the pipeline"}) - data_assets: Optional[list[DataAssetsRunParam]] = field(default=None, metadata={"description": "List of data assets to attach with their mount paths"}) - parameters: Optional[list[str]] = field(default=None, metadata={"description": "Ordered list of parameter values"}) - named_parameters: Optional[list[NamedRunParam]] = field(default=None, metadata={"description": "Named parameters for the computation"}) - processes: Optional[list[PipelineProcessParams]] = field(default=None, metadata={"description": "Process-specific parameters for pipeline runs"}) - nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile configuration"}) + + capsule_id: Optional[str] = field( + default=None, + metadata={ + "description": "ID of the capsule to run (required for capsule runs)" + }, + ) + pipeline_id: Optional[str] = field( + default=None, + metadata={ + "description": "ID of the pipeline to run (required for pipeline runs)" + }, + ) + version: Optional[int] = field( + default=None, metadata={"description": "Specific version of the capsule to run"} + ) + resume_run_id: Optional[str] = field( + default=None, + metadata={"description": "ID of a previous computation to resume from"}, + ) + nextflow_profile: Optional[str] = field( + default=None, + metadata={"description": "Pipeline Nextflow profile configuration"}, + ) + data_assets: Optional[list[DataAssetsRunParam]] = field( + default=None, + metadata={ + "description": "List of data assets to attach with their mount paths" + }, + ) + parameters: Optional[list[str]] = field( + default=None, metadata={"description": "Ordered list of parameter values"} + ) + named_parameters: Optional[list[NamedRunParam]] = field( + default=None, metadata={"description": "Named parameters for the computation"} + ) + processes: Optional[list[PipelineProcessParams]] = field( + default=None, + metadata={"description": "Process-specific parameters for pipeline runs"}, + ) @dataclass @@ -130,15 +244,15 @@ def get_computation(self, computation_id: str) -> Computation: def run_capsule(self, run_params: RunParams) -> Computation: """ Execute a capsule or pipeline with specified parameters and data assets. - - For capsule execution: Set run_params.capsule_id and optionally provide data_assets, + + For capsule execution: Set run_params.capsule_id and optionally provide data_assets, parameters, or named_parameters. - + For pipeline execution: Set run_params.pipeline_id and optionally provide data_assets, processes (with process-specific parameters), and nextflow_profile configuration. - - Typical workflow: 1) run_capsule() to start execution, 2) wait_until_completed() to - monitor progress, 3) list_computation_results() and get_result_file_download_url() + + Typical workflow: 1) run_capsule() to start execution, 2) wait_until_completed() to + monitor progress, 3) list_computation_results() and get_result_file_download_url() to retrieve outputs. """ res = self.client.post("computations", json=run_params.to_dict()) @@ -153,15 +267,15 @@ def wait_until_completed( ) -> Computation: """ Poll a computation until it reaches 'Completed' or 'Failed' state with configurable timing. - + Args: computation: The computation object to monitor polling_interval: Time between status checks in seconds (minimum 5 seconds) timeout: Maximum time to wait in seconds, or None for no timeout - + Returns: Updated computation object once completed or failed - + Raises: ValueError: If polling_interval < 5 or timeout constraints are violated TimeoutError: If computation doesn't complete within the timeout period @@ -186,7 +300,9 @@ def wait_until_completed( return comp if timeout is not None and (time() - t0) > timeout: - raise TimeoutError(f"Computation {computation.id} did not complete within {timeout} seconds") + raise TimeoutError( + f"Computation {computation.id} did not complete within {timeout} seconds" + ) sleep(polling_interval) @@ -200,7 +316,9 @@ def list_computation_results(self, computation_id: str, path: str = "") -> Folde return Folder.from_dict(res.json()) - def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL: + def get_result_file_download_url( + self, computation_id: str, path: str + ) -> DownloadFileURL: """Generate a download URL for a specific result file from a computation.""" res = self.client.get( f"computations/{computation_id}/results/download_url", @@ -215,7 +333,4 @@ def delete_computation(self, computation_id: str): def rename_computation(self, computation_id: str, name: str): """Rename an existing computation with a new display name.""" - self.client.patch( - f"computations/{computation_id}", - params={"name": name} - ) + self.client.patch(f"computations/{computation_id}", params={"name": name}) diff --git a/src/codeocean/folder.py b/src/codeocean/folder.py index ea03185..954e2cc 100644 --- a/src/codeocean/folder.py +++ b/src/codeocean/folder.py @@ -9,28 +9,40 @@ @dataclass(frozen=True) class FolderItem: """Represents a file or folder item within a folder listing.""" + name: str = field(metadata={"description": "Name of the file or folder"}) path: str = field(metadata={"description": "Path of the file or folder"}) type: str = field(metadata={"description": "Item type ('file' or 'folder')"}) - size: Optional[int] = field(default=None, metadata={"description": "Size in bytes (only for files)"}) + size: Optional[int] = field( + default=None, metadata={"description": "Size in bytes (only for files)"} + ) @dataclass_json @dataclass(frozen=True) class Folder: """Represents a folder with its list of items (files and subfolders).""" - items: list[FolderItem] = field(metadata={"description": "List of items in the folder (files and subfolders)"}) + + items: list[FolderItem] = field( + metadata={"description": "List of items in the folder (files and subfolders)"} + ) @dataclass_json @dataclass(frozen=True) class ListFolderParams: """Parameters for listing contents of a folder.""" - path: str = field(metadata={"description": "Path of the folder to list; empty string for root"}) + + path: str = field( + metadata={"description": "Path of the folder to list; empty string for root"} + ) @dataclass_json @dataclass(frozen=True) class DownloadFileURL: """Download URL information for retrieving a file.""" - url: str = field(metadata={"description": "Pre-signed URL for downloading the specified file"}) + + url: str = field( + metadata={"description": "Pre-signed URL for downloading the specified file"} + ) From 7165624381f1dad7ceb6519769dffc282a3f0d04 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 13:00:06 +0300 Subject: [PATCH 09/19] linter issues - shorter lines --- src/codeocean/capsule.py | 44 ++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index ea2eb69..ac6b605 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -29,7 +29,8 @@ class CapsuleSortBy(StrEnum): @dataclass_json @dataclass(frozen=True) class OriginalCapsuleInfo: - """Information about the original capsule when this capsule is cloned from another.""" + """Information about the original capsule when this capsule is cloned from + another.""" id: Optional[str] = dataclass_field( default=None, metadata={"description": "Original capsule ID"} @@ -73,7 +74,9 @@ class Capsule: article: Optional[dict] = dataclass_field( default=None, metadata={ - "description": "Capsule article info with URL, ID, DOI, citation, state, name, journal_name, and publish_time" + "description": "Capsule article info with URL, ID, DOI, " + "citation, state, name, journal_name, and " + "publish_time" }, ) cloned_from_url: Optional[str] = dataclass_field( @@ -93,7 +96,8 @@ class Capsule: original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field( default=None, metadata={ - "description": "Original capsule info when this is cloned from another capsule" + "description": "Original capsule info when this is cloned from " + "another capsule" }, ) release_capsule: Optional[str] = dataclass_field( @@ -102,13 +106,16 @@ class Capsule: submission: Optional[dict] = dataclass_field( default=None, metadata={ - "description": "Submission info with timestamp, commit hash, verification_capsule, verified status, and verified_timestamp" + "description": "Submission info with timestamp, commit hash, " + "verification_capsule, verified status, and " + "verified_timestamp" }, ) versions: Optional[list[dict]] = dataclass_field( default=None, metadata={ - "description": "Capsule versions with major_version, minor_version, release_time, and DOI" + "description": "Capsule versions with major_version, " + "minor_version, release_time, and DOI" }, ) @@ -116,40 +123,44 @@ class Capsule: @dataclass_json @dataclass(frozen=True) class CapsuleSearchParams: - """Parameters for searching capsules with various filters and pagination options.""" + """Parameters for searching capsules with various filters and pagination + options.""" query: Optional[str] = dataclass_field( default=None, metadata={ - "description": "Search query in free text or structured format (name:... tag:...)" + "description": "Search query in free text or structured format " + "(name:... tag:...)" }, ) next_token: Optional[str] = dataclass_field( default=None, metadata={ - "description": "Token for next page of results from previous response" + "description": "Token for next page of results from previous " "response" }, ) offset: Optional[int] = dataclass_field( default=None, metadata={ - "description": "Starting index for search results (ignored if next_token is set)" + "description": "Starting index for search results (ignored if " + "next_token is set)" }, ) limit: Optional[int] = dataclass_field( default=None, metadata={ - "description": "Number of items to return (up to 1000, defaults to 100)" + "description": "Number of items to return (up to 1000, " "defaults to 100)" }, ) sort_field: Optional[CapsuleSortBy] = dataclass_field( default=None, - metadata={"description": "Field to sort by (created, name, last_accessed)"}, + metadata={"description": "Field to sort by (created, name, " "last_accessed)"}, ) sort_order: Optional[SortOrder] = dataclass_field( default=None, metadata={ - "description": "Sort order ('asc' or 'desc') - must be provided with a sort_field parameter as well!" + "description": "Sort order ('asc' or 'desc') - must be provided " + "with a sort_field parameter as well!" }, ) ownership: Optional[Ownership] = dataclass_field( @@ -161,7 +172,8 @@ class CapsuleSearchParams: status: Optional[CapsuleStatus] = dataclass_field( default=None, metadata={ - "description": "Filter by status (release or non_release) - defaults to all" + "description": "Filter by status (release or non_release) - " + "defaults to all" }, ) favorite: Optional[bool] = dataclass_field( @@ -173,7 +185,8 @@ class CapsuleSearchParams: filters: Optional[list[SearchFilter]] = dataclass_field( default=None, metadata={ - "description": "Additional field-level filters for name, description, tags, or custom fields" + "description": "Additional field-level filters for name, " + "description, tags, or custom fields" }, ) @@ -241,7 +254,8 @@ def detach_data_assets(self, capsule_id: str, data_assets: list[str]): def search_capsules( self, search_params: CapsuleSearchParams ) -> CapsuleSearchResults: - """Search for capsules with filtering, sorting, and pagination options.""" + """Search for capsules with filtering, sorting, and pagination + options.""" res = self.client.post("capsules/search", json=search_params.to_dict()) return CapsuleSearchResults.from_dict(res.json()) From c837022c2c0ba99175d996babec032434b8b8fe5 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 13:18:33 +0300 Subject: [PATCH 10/19] refactor: linting fix --- src/codeocean/data_asset.py | 719 +++++++++++++++++++++++++++++------- 1 file changed, 577 insertions(+), 142 deletions(-) diff --git a/src/codeocean/data_asset.py b/src/codeocean/data_asset.py index 8e27ef2..a5e9243 100644 --- a/src/codeocean/data_asset.py +++ b/src/codeocean/data_asset.py @@ -14,6 +14,7 @@ class DataAssetType(StrEnum): """Type of data asset indicating its content and purpose.""" + Dataset = "dataset" Result = "result" Combined = "combined" @@ -22,6 +23,7 @@ class DataAssetType(StrEnum): class DataAssetState(StrEnum): """Current state of a data asset during its creation and lifecycle.""" + Draft = "draft" Ready = "ready" Failed = "failed" @@ -31,16 +33,37 @@ class DataAssetState(StrEnum): @dataclass(frozen=True) class Provenance: """Shows the data asset provenance information when type is result.""" - commit: Optional[str] = field(default=None, metadata={"description": "Commit hash the data asset was created from"}) - run_script: Optional[str] = field(default=None, metadata={"description": "Script path the data asset was created by"}) - docker_image: Optional[str] = field(default=None, metadata={"description": "Docker image used to create the data asset"}) - capsule: Optional[str] = field(default=None, metadata={"description": "Capsule used to create the data asset"}) - data_assets: Optional[list[str]] = field(default=None, metadata={"description": "Data assets that were used to create this data asset"}) - computation: Optional[str] = field(default=None, metadata={"description": "Computation ID used to create the data asset"}) + + commit: Optional[str] = field( + default=None, + metadata={"description": "Commit hash the data asset was created from"}, + ) + run_script: Optional[str] = field( + default=None, + metadata={"description": "Script path the data asset was created by"}, + ) + docker_image: Optional[str] = field( + default=None, + metadata={"description": "Docker image used to create the data asset"}, + ) + capsule: Optional[str] = field( + default=None, metadata={"description": "Capsule used to create the data asset"} + ) + data_assets: Optional[list[str]] = field( + default=None, + metadata={ + "description": "Data assets that were used to create this data asset" + }, + ) + computation: Optional[str] = field( + default=None, + metadata={"description": "Computation ID used to create the data asset"}, + ) class DataAssetOrigin(StrEnum): """Origin type of the data asset indicating where it was created from.""" + Local = "local" AWS = "aws" GCP = "gcp" @@ -50,161 +73,430 @@ class DataAssetOrigin(StrEnum): @dataclass(frozen=True) class SourceBucket: """Information about the bucket from which the data asset was created.""" - origin: DataAssetOrigin = field(metadata={"description": "Origin type (aws, local, gcp)"}) - bucket: Optional[str] = field(default=None, metadata={"description": "The original bucket's name"}) - prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the S3 bucket from which the data asset was created"}) - external: Optional[bool] = field(default=None, metadata={"description": "Indicates if the data asset is stored externally"}) + + origin: DataAssetOrigin = field( + metadata={"description": "Origin type (aws, local, gcp)"} + ) + bucket: Optional[str] = field( + default=None, metadata={"description": "The original bucket's name"} + ) + prefix: Optional[str] = field( + default=None, + metadata={ + "description": ( + "The folder in the S3 bucket from which " + "the data asset was created" + ) + }, + ) + external: Optional[bool] = field( + default=None, + metadata={"description": "Indicates if the data asset is stored externally"}, + ) @dataclass_json @dataclass(frozen=True) class AppParameter: """Name and value of app panel parameters used to generate result data assets.""" - name: Optional[str] = field(default=None, metadata={"description": "Parameter name"}) - value: Optional[str] = field(default=None, metadata={"description": "Parameter value"}) + + name: Optional[str] = field( + default=None, metadata={"description": "Parameter name"} + ) + value: Optional[str] = field( + default=None, metadata={"description": "Parameter value"} + ) @dataclass_json @dataclass(frozen=True) class ResultsInfo: - """Additional information for data assets created from exported capsule/pipeline results.""" - capsule_id: Optional[str] = field(default=None, metadata={"description": "ID of the capsule that was executed"}) - pipeline_id: Optional[str] = field(default=None, metadata={"description": "ID of the pipeline that was executed"}) - version: Optional[int] = field(default=None, metadata={"description": "Capsule or pipeline release version"}) - commit: Optional[str] = field(default=None, metadata={"description": "Commit hash of capsule/pipeline code at time of execution"}) - run_script: Optional[str] = field(default=None, metadata={"description": "Path to the script that was executed relative to /Capsule folder"}) - data_assets: Optional[list[str]] = field(default=None, metadata={"description": "IDs of data assets used during the run"}) - parameters: Optional[list[Param]] = field(default=None, metadata={"description": "Run parameters used for execution"}) - nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile"}) - processes: Optional[list[PipelineProcess]] = field(default=None, metadata={"description": "Pipeline processes information"}) + """ + Additional information for data assets created from exported capsule/pipeline + results. + """ + + capsule_id: Optional[str] = field( + default=None, metadata={"description": "ID of the capsule that was executed"} + ) + pipeline_id: Optional[str] = field( + default=None, metadata={"description": "ID of the pipeline that was executed"} + ) + version: Optional[int] = field( + default=None, metadata={"description": "Capsule or pipeline release version"} + ) + commit: Optional[str] = field( + default=None, + metadata={ + "description": "Commit hash of capsule/pipeline code at time of execution" + }, + ) + run_script: Optional[str] = field( + default=None, + metadata={ + "description": "Path to the script that was executed relative to" + " /Capsule folder" + }, + ) + data_assets: Optional[list[str]] = field( + default=None, + metadata={"description": "IDs of data assets used during the run"} + ) + parameters: Optional[list[Param]] = field( + default=None, + metadata={ + "description": "Run parameters used for execution" + } + ) + nextflow_profile: Optional[str] = field( + default=None, + metadata={"description": "Pipeline Nextflow profile"}, + ) + processes: Optional[list[PipelineProcess]] = field( + default=None, metadata={"description": "Pipeline processes information"} + ) @dataclass_json @dataclass(frozen=True) class DataAsset: """Represents a Code Ocean data asset with its metadata and properties.""" + id: str = field(metadata={"description": "Unique data asset ID (UUID string)"}) - created: int = field(metadata={"description": "Data asset creation time (seconds since epoch)"}) + created: int = field( + metadata={"description": "Data asset creation time (seconds since epoch)"} + ) name: str = field(metadata={"description": "Name of the data asset"}) - mount: str = field(metadata={"description": "The default mount folder of the data asset"}) - state: DataAssetState = field(metadata={"description": "Data asset creation state (draft, ready, failed)"}) - type: DataAssetType = field(metadata={"description": "Type of the data asset (dataset, result, combined, model)"}) - last_used: int = field(metadata={"description": "Time data asset was last used in seconds from unix epoch"}) - files: Optional[int] = field(default=None, metadata={"description": "Number of files in the data asset"}) - size: Optional[int] = field(default=None, metadata={"description": "Size in bytes of the data asset (parsed to int)"}) - description: Optional[str] = field(default=None, metadata={"description": "Data asset description"}) - tags: Optional[list[str]] = field(default=None, metadata={"description": "Keywords for searching the data asset"}) - provenance: Optional[Provenance] = field(default=None, metadata={"description": "Shows the data asset provenance if type is result; only 'capsule' or 'computation' field will be populated depending on source"}) - source_bucket: Optional[SourceBucket] = field(default=None, metadata={"description": "Information on bucket from which data asset was created"}) - custom_metadata: Optional[dict] = field(default=None, metadata={"description": "Custom metadata fields defined by deployment admin with user-set values"}) - app_parameters: Optional[list[AppParameter]] = field(default=None, metadata={"description": "Name and value of app panel parameters used to generate result data asset"}) - nextflow_profile: Optional[str] = field(default=None, metadata={"description": "Pipeline Nextflow profile"}) - contained_data_assets: Optional[list[ContainedDataAsset]] = field(default=None, metadata={"description": "List of contained data assets if type is combined"}) - last_transferred: Optional[int] = field(default=None, metadata={"description": "Time data asset's files were last transferred to a different S3 storage location"}) - transfer_error: Optional[str] = field(default=None, metadata={"description": "The error that occurred during the last transfer attempt if it failed"}) - failure_reason: Optional[str] = field(default=None, metadata={"description": "Reason for data asset creation failure if state is failed"}) + mount: str = field( + metadata={"description": "The default mount folder of the data asset"} + ) + state: DataAssetState = field( + metadata={"description": "Data asset creation state (draft, ready, failed)"} + ) + type: DataAssetType = field( + metadata={ + "description": "Type of the data asset (dataset, result, combined, model)" + } + ) + last_used: int = field( + metadata={ + "description": "Time data asset was last used in seconds from unix epoch" + } + ) + files: Optional[int] = field( + default=None, metadata={"description": "Number of files in the data asset"} + ) + size: Optional[int] = field( + default=None, + metadata={"description": "Size in bytes of the data asset (parsed to int)"}, + ) + description: Optional[str] = field( + default=None, metadata={"description": "Data asset description"} + ) + tags: Optional[list[str]] = field( + default=None, metadata={"description": "Keywords for searching the data asset"} + ) + provenance: Optional[Provenance] = field( + default=None, + metadata={ + "description": "Shows the data asset provenance if type is result; only " + "'capsule' or 'computation' field will be populated depending on source" + }, + ) + source_bucket: Optional[SourceBucket] = field( + default=None, + metadata={ + "description": "Information on bucket from which data asset was created" + }, + ) + custom_metadata: Optional[dict] = field( + default=None, + metadata={ + "description": "Custom metadata fields defined by deployment admin with " + "user-set values" + }, + ) + app_parameters: Optional[list[AppParameter]] = field( + default=None, + metadata={ + "description": "Name and value of app panel parameters used to generate " + "result data asset" + }, + ) + nextflow_profile: Optional[str] = field( + default=None, + metadata={"description": "Pipeline Nextflow profile"}, + ) + contained_data_assets: Optional[list[ContainedDataAsset]] = field( + default=None, + metadata={"description": "List of contained data assets if type is combined"}, + ) + last_transferred: Optional[int] = field( + default=None, + metadata={ + "description": "Time data asset's files were last transferred to a " + "different S3 storage location" + }, + ) + transfer_error: Optional[str] = field( + default=None, + metadata={ + "description": "The error that occurred during the last transfer attempt " + "if it failed" + }, + ) + failure_reason: Optional[str] = field( + default=None, + metadata={ + "description": "Reason for data asset creation failure if state is failed" + }, + ) @dataclass_json @dataclass(frozen=True) class DataAssetUpdateParams: """Parameters for updating data asset metadata.""" - name: Optional[str] = field(default=None, metadata={"description": "Data asset name"}) - description: Optional[str] = field(default=None, metadata={"description": "Data asset description"}) - tags: Optional[list[str]] = field(default=None, metadata={"description": "Keywords for searching the data asset"}) - mount: Optional[str] = field(default=None, metadata={"description": "Default mount folder of the data asset"}) - custom_metadata: Optional[dict] = field(default=None, metadata={"description": "Custom metadata fields with user-set values"}) + + name: Optional[str] = field( + default=None, metadata={"description": "Data asset name"} + ) + description: Optional[str] = field( + default=None, metadata={"description": "Data asset description"} + ) + tags: Optional[list[str]] = field( + default=None, metadata={"description": "Keywords for searching the data asset"} + ) + mount: Optional[str] = field( + default=None, metadata={"description": "Default mount folder of the data asset"} + ) + custom_metadata: Optional[dict] = field( + default=None, + metadata={"description": "Custom metadata fields with user-set values"}, + ) @dataclass_json @dataclass(frozen=True) class AWSS3Source: """AWS S3 source configuration for creating data assets.""" - bucket: str = field(metadata={"description": "The S3 bucket from which the data asset will be created"}) - prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the S3 bucket from which the data asset will be created"}) - keep_on_external_storage: Optional[bool] = field(default=None, metadata={"description": "When true, data asset files will not be copied to Code Ocean"}) - public: Optional[bool] = field(default=None, metadata={"description": "When true, Code Ocean will access the source bucket without credentials"}) + + bucket: str = field( + metadata={ + "description": "The S3 bucket from which the data asset will be created" + } + ) + prefix: Optional[str] = field( + default=None, + metadata={ + "description": ( + "The folder in the S3 bucket from which " + "the data asset will be created" + ) + }, + ) + keep_on_external_storage: Optional[bool] = field( + default=None, + metadata={ + "description": ( + "When true, data asset files will not be copied to " + "Code Ocean" + ) + }, + ) + public: Optional[bool] = field( + default=None, + metadata={ + "description": ( + "When true, Code Ocean will access the source bucket " + "without credentials" + ) + }, + ) @dataclass_json @dataclass(frozen=True) class GCPCloudStorageSource: - """Google Cloud Platform Cloud Storage source configuration for creating data assets.""" - bucket: str = field(metadata={"description": "The GCP Cloud Storage bucket from which the data asset will be created"}) - client_id: Optional[str] = field(default=None, metadata={"description": "GCP client ID for authentication"}) - client_secret: Optional[str] = field(default=None, metadata={"description": "GCP client secret for authentication"}) - prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the GCP bucket from which the data asset will be created"}) + """ + Google Cloud Platform Cloud Storage source configuration for creating + data assets. + """ + + bucket: str = field( + metadata={ + "description": "The GCP Cloud Storage bucket from which the" + " data asset will be created" + } + ) + client_id: Optional[str] = field( + default=None, metadata={"description": "GCP client ID for authentication"} + ) + client_secret: Optional[str] = field( + default=None, metadata={"description": "GCP client secret for authentication"} + ) + prefix: Optional[str] = field( + default=None, + metadata={ + "description": ( + "The folder in the GCP bucket from which " + "the data asset will be created" + ) + }, + ) @dataclass_json @dataclass(frozen=True) class ComputationSource: """Computation source configuration for creating result data assets.""" - id: str = field(metadata={"description": "Computation ID from which to create the data asset"}) - path: Optional[str] = field(default=None, metadata={"description": "Results path within computation (empty captures all result files)"}) + + id: str = field( + metadata={"description": "Computation ID from which to create the data asset"} + ) + path: Optional[str] = field( + default=None, + metadata={ + "description": ( + "Results path within computation " + "(empty captures all result files)" + ) + }, + ) @dataclass_json @dataclass(frozen=True) class Source: """Source configuration for data asset creation from various origins.""" - aws: Optional[AWSS3Source] = field(default=None, metadata={"description": "AWS S3 source configuration"}) - gcp: Optional[GCPCloudStorageSource] = field(default=None, metadata={"description": "GCP Cloud Storage source configuration"}) - computation: Optional[ComputationSource] = field(default=None, metadata={"description": "Computation source configuration"}) + + aws: Optional[AWSS3Source] = field( + default=None, metadata={"description": "AWS S3 source configuration"} + ) + gcp: Optional[GCPCloudStorageSource] = field( + default=None, metadata={"description": "GCP Cloud Storage source configuration"} + ) + computation: Optional[ComputationSource] = field( + default=None, metadata={"description": "Computation source configuration"} + ) @dataclass_json @dataclass(frozen=True) class AWSS3Target: """AWS S3 target configuration for external data asset storage.""" - bucket: str = field(metadata={"description": "The S3 bucket where the data asset will be stored"}) - prefix: Optional[str] = field(default=None, metadata={"description": "The folder in the S3 bucket where the data asset will be placed"}) + + bucket: str = field( + metadata={"description": "The S3 bucket where the data asset will be stored"} + ) + prefix: Optional[str] = field( + default=None, + metadata={ + "description": ( + "The folder in the S3 bucket where the data asset " + "will be placed" + ) + }, + ) @dataclass_json @dataclass(frozen=True) class Target: """Target configuration for external data asset storage.""" - aws: Optional[AWSS3Target] = field(default=None, metadata={"description": "AWS S3 target configuration"}) + + aws: Optional[AWSS3Target] = field( + default=None, metadata={"description": "AWS S3 target configuration"} + ) @dataclass_json @dataclass(frozen=True) class DataAssetParams: - """Complete parameter set for creating data assets with various source types and configurations.""" + """ + Complete parameter set for creating data assets with various source types + and configurations. + """ + name: str = field(metadata={"description": "Data asset name"}) - tags: list[str] = field(metadata={"description": "Keywords applied to the data asset to aid in searching"}) + tags: list[str] = field( + metadata={ + "description": "Keywords applied to the data asset to aid in searching" + } + ) mount: str = field(metadata={"description": "Data asset default mount folder"}) - description: Optional[str] = field(default=None, metadata={"description": "Data asset description"}) - source: Optional[Source] = field(default=None, metadata={"description": "Source configuration (AWS S3, GCP, or computation)"}) - target: Optional[Target] = field(default=None, metadata={"description": "Target configuration for external storage"}) - custom_metadata: Optional[dict] = field(default=None, metadata={"description": "Custom metadata fields according to admin-defined fields"}) - data_asset_ids: Optional[list[str]] = field(default=None, metadata={"description": "List of data asset IDs for creating combined data assets"}) - results_info: Optional[ResultsInfo] = field(default=None, metadata={"description": "Additional information for data assets from external results"}) + description: Optional[str] = field( + default=None, metadata={"description": "Data asset description"} + ) + source: Optional[Source] = field( + default=None, + metadata={"description": "Source configuration (AWS S3, GCP, or computation)"}, + ) + target: Optional[Target] = field( + default=None, + metadata={"description": "Target configuration for external storage"}, + ) + custom_metadata: Optional[dict] = field( + default=None, + metadata={ + "description": "Custom metadata fields according to admin-defined fields" + }, + ) + data_asset_ids: Optional[list[str]] = field( + default=None, + metadata={ + "description": "List of data asset IDs for creating combined data assets" + }, + ) + results_info: Optional[ResultsInfo] = field( + default=None, + metadata={ + "description": "Additional information for " + "data assets from external results" + }, + ) @dataclass_json @dataclass(frozen=True) class DataAssetAttachParams: """Parameters for attaching data assets to capsules.""" + id: str = field(metadata={"description": "Data asset ID to attach"}) - mount: Optional[str] = field(default=None, metadata={"description": "Mount path for the attached data asset"}) + mount: Optional[str] = field( + default=None, metadata={"description": "Mount path for the attached data asset"} + ) @dataclass_json @dataclass(frozen=True) class DataAssetAttachResults: """Results from attaching data assets to capsules.""" + id: str = field(metadata={"description": "Data asset ID that was attached"}) - mount_state: Optional[str] = field(default=None, metadata={"description": "Current state of the data asset mount"}) - job_id: Optional[str] = field(default=None, metadata={"description": "Job ID for the attachment operation"}) - external: Optional[bool] = field(default=None, metadata={"description": "Indicates if the data asset is external"}) - ready: Optional[bool] = field(default=None, metadata={"description": "Indicates if the data asset is ready for use"}) - mount: Optional[str] = field(default=None, metadata={"description": "Actual mount path used for the data asset"}) + mount_state: Optional[str] = field( + default=None, metadata={"description": "Current state of the data asset mount"} + ) + job_id: Optional[str] = field( + default=None, metadata={"description": "Job ID for the attachment operation"} + ) + external: Optional[bool] = field( + default=None, + metadata={"description": "Indicates if the data asset is external"}, + ) + ready: Optional[bool] = field( + default=None, + metadata={"description": "Indicates if the data asset is ready for use"}, + ) + mount: Optional[str] = field( + default=None, + metadata={"description": "Actual mount path used for the data asset"}, + ) class DataAssetSortBy(StrEnum): """Fields available for sorting data asset search results.""" + Created = "created" Type = "type" Name = "name" @@ -213,6 +505,7 @@ class DataAssetSortBy(StrEnum): class DataAssetSearchOrigin(StrEnum): """Origin filter for data asset searches.""" + Internal = "internal" External = "external" @@ -220,45 +513,146 @@ class DataAssetSearchOrigin(StrEnum): @dataclass_json @dataclass(frozen=True) class DataAssetSearchParams: - """Parameters for searching data assets with filtering, sorting, and pagination options.""" - query: Optional[str] = field(default=None, metadata={"description": "Search query in free text or structured format (name:... tag:... run_script:... commit_id:...)"}) - next_token: Optional[str] = field(default=None, metadata={"description": "Token for next page of results from previous response"}) - offset: Optional[int] = field(default=None, metadata={"description": "Starting index for search results (ignored if next_token is set)"}) - limit: Optional[int] = field(default=None, metadata={"description": "Number of items to return (up to 1000, defaults to 100)"}) - sort_field: Optional[DataAssetSortBy] = field(default=None, metadata={"description": "Field to sort by (created, type, name, size)"}) - sort_order: Optional[SortOrder] = field(default=None, metadata={"description": "Sort order (asc or desc) - must be provided with sort_field"}) - type: Optional[DataAssetType] = field(default=None, metadata={"description": "Filter by data asset type (dataset, result, combined, model)"}) - ownership: Optional[Ownership] = field(default=None, metadata={"description": "Filter by ownership (created or shared) - defaults to all accessible"}) - origin: Optional[DataAssetSearchOrigin] = field(default=None, metadata={"description": "Filter by origin (internal or external)"}) - favorite: Optional[bool] = field(default=None, metadata={"description": "Search only favorite data assets"}) - archived: Optional[bool] = field(default=None, metadata={"description": "Search only archived data assets"}) - filters: Optional[list[SearchFilter]] = field(default=None, metadata={"description": "Additional field-level filters for name, description, tags, or custom fields"}) + """ + Parameters for searching data assets with filtering, sorting, + and pagination options. + """ + + query: Optional[str] = field( + default=None, + metadata={ + "description": "Search query in free text or structured format " + "(name:... tag:... run_script:... commit_id:...)" + }, + ) + next_token: Optional[str] = field( + default=None, + metadata={ + "description": "Token for next page of results from previous response" + }, + ) + offset: Optional[int] = field( + default=None, + metadata={ + "description": ( + "Starting index for search results " + "(ignored if next_token is set)" + ) + }, + ) + limit: Optional[int] = field( + default=None, + metadata={ + "description": "Number of items to return (up to 1000, defaults to 100)" + }, + ) + sort_field: Optional[DataAssetSortBy] = field( + default=None, + metadata={"description": "Field to sort by (created, type, name, size)"}, + ) + sort_order: Optional[SortOrder] = field( + default=None, + metadata={ + "description": "Sort order (asc or desc) - must be provided with sort_field" + }, + ) + type: Optional[DataAssetType] = field( + default=None, + metadata={ + "description": "Filter by data asset type " + "(dataset, result, combined, model)" + }, + ) + ownership: Optional[Ownership] = field( + default=None, + metadata={ + "description": ( + "Filter by ownership (created or shared) - " + "defaults to all accessible" + ) + }, + ) + origin: Optional[DataAssetSearchOrigin] = field( + default=None, + metadata={"description": "Filter by origin (internal or external)"}, + ) + favorite: Optional[bool] = field( + default=None, metadata={"description": "Search only favorite data assets"} + ) + archived: Optional[bool] = field( + default=None, metadata={"description": "Search only archived data assets"} + ) + filters: Optional[list[SearchFilter]] = field( + default=None, + metadata={ + "description": "Additional field-level filters " + "for name, description, tags, " + "or custom fields" + }, + ) @dataclass_json @dataclass(frozen=True) class DataAssetSearchResults: """Results from a data asset search operation with pagination support.""" - has_more: bool = field(metadata={"description": "Indicates if there are more results available"}) - results: list[DataAsset] = field(metadata={"description": "Array of data assets found matching the search criteria"}) - next_token: Optional[str] = field(default=None, metadata={"description": "Token for fetching the next page of results"}) + + has_more: bool = field( + metadata={ + "description": "Indicates if there are more results available" + } + ) + results: list[DataAsset] = field( + metadata={ + "description": ( + "Array of data assets found matching the search criteria" + ) + } + ) + next_token: Optional[str] = field( + default=None, + metadata={"description": "Token for fetching the next page of results"}, + ) @dataclass_json @dataclass(frozen=True) class ContainedDataAsset: """Information about data assets contained within a combined data asset.""" - id: Optional[str] = field(default=None, metadata={"description": "ID of the contained data asset"}) - mount: Optional[str] = field(default=None, metadata={"description": "Mount path of the contained data asset"}) - size: Optional[int] = field(default=None, metadata={"description": "Size in bytes of the contained data asset"}) + + id: Optional[str] = field( + default=None, metadata={"description": "ID of the contained data asset"} + ) + mount: Optional[str] = field( + default=None, + metadata={"description": "Mount path of the contained data asset"} + ) + size: Optional[int] = field( + default=None, + metadata={"description": "Size in bytes of the contained data asset"}, + ) @dataclass_json @dataclass(frozen=True) class TransferDataParams: - """Parameters for transferring data asset files to different S3 storage locations.""" - target: Target = field(metadata={"description": "Target storage location configuration"}) - force: Optional[bool] = field(default=None, metadata={"description": "Perform transfer even if there are release pipelines using the data asset"}) + """Parameters for transferring data asset files + to different S3 storage locations.""" + + target: Target = field( + metadata={ + "description": "Target storage location configuration" + } + ) + force: Optional[bool] = field( + default=None, + metadata={ + "description": ( + "Perform transfer even if there are release pipelines " + "using the data asset" + ) + }, + ) @dataclass @@ -273,35 +667,48 @@ def get_data_asset(self, data_asset_id: str) -> DataAsset: return DataAsset.from_dict(res.json()) - def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset: + def update_metadata( + self, data_asset_id: str, update_params: DataAssetUpdateParams + ) -> DataAsset: """ - Update metadata for a data asset including name, description, tags, mount, and custom metadata. - + Update metadata for a data asset including name, description, tags, mount, + and custom metadata. + Supports updating various metadata types: - Basic metadata: name (display name), description (free text description) - Organization: tags (keywords for searching), mount (default mount folder path) - - Custom metadata: admin-defined custom fields with user-set values according to - deployment configuration (string, number, or date fields in unix epoch format) + - Custom metadata: admin-defined custom fields with user-set values according to + deployment configuration + (string, number, or date fields in unix epoch format) """ - res = self.client.put(f"data_assets/{data_asset_id}", json=update_params.to_dict()) + res = self.client.put( + f"data_assets/{data_asset_id}", + json=update_params.to_dict() + ) return DataAsset.from_dict(res.json()) def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset: """ - Create a new data asset from various sources including S3 buckets, computation results, or combined assets. - - Data assets are versioned, immutable collections of files that serve as inputs or outputs - for computational workflows in Code Ocean. Internal data assets store files within Code Ocean's - infrastructure, while external data assets reference files in external storage (S3/GCP) without copying. - - Supports creating data assets from AWS S3, GCP Cloud Storage, computation results, or combining - existing data assets. Returns confirmation of creation request validity, not success, as creation - takes time. Use wait_until_ready() to monitor creation progress. - - Typical workflow: 1) create_data_asset() to initiate creation, 2) wait_until_ready() to - monitor progress until state is 'ready', 3) get_data_asset() to fetch final metadata, - 4) list_data_asset_files() and get_data_asset_file_download_url() to access contents. + Create a new data asset from various sources including S3 buckets, + computation results, or combined assets. + + Data assets are versioned, immutable collections of files that serve as inputs + or outputs for computational workflows in Code Ocean. Internal data assets + store files within Code Ocean's infrastructure, while external data assets + reference files in external storage (S3/GCP) without copying. + + Supports creating data assets from AWS S3, GCP Cloud Storage, computation + results, or combining existing data assets. Returns confirmation of creation + request validity, not success, as creation takes time. Use wait_until_ready() + to monitor creation progress. + + Typical workflow: + 1) create_data_asset() to initiate creation + 2) wait_until_ready() to monitor progress until state is 'ready' + 3) get_data_asset() to fetch final metadata + 4) list_data_asset_files() and get_data_asset_file_download_url() + to access contents """ res = self.client.post("data_assets", json=data_asset_params.to_dict()) @@ -314,31 +721,36 @@ def wait_until_ready( timeout: float | None = None, ) -> DataAsset: """ - Poll a data asset until it reaches 'Ready' or 'Failed' state with configurable timing. - + Poll a data asset until it reaches 'Ready' or 'Failed' state with configurable + timing. + Args: data_asset: The data asset object to monitor - polling_interval: Time between status checks in seconds (minimum 5 seconds) + polling_interval: Time between status checks in seconds + (minimum 5 seconds) timeout: Maximum time to wait in seconds, or None for no timeout - + Returns: Updated data asset object once ready or failed - + Raises: ValueError: If polling_interval < 5 or timeout constraints are violated - TimeoutError: If data asset doesn't become ready within the timeout period + TimeoutError: If data asset doesn't become ready within timeout period """ if polling_interval < 5: raise ValueError( - f"Polling interval {polling_interval} should be greater than or equal to 5" + f"Polling interval {polling_interval} " + "should be greater than or equal to 5" ) if timeout is not None and timeout < polling_interval: raise ValueError( - f"Timeout {timeout} should be greater than or equal to polling interval {polling_interval}" + f"Timeout {timeout} should be greater than or equal to " + f"polling interval {polling_interval}" ) if timeout is not None and timeout < 0: raise ValueError( - f"Timeout {timeout} should be greater than or equal to 0 (seconds), or None" + f"Timeout {timeout} should be greater than or equal to" + " 0 (seconds), or None" ) t0 = time() while True: @@ -348,9 +760,13 @@ def wait_until_ready( return da if timeout is not None and (time() - t0) > timeout: - raise TimeoutError(f"Data asset {data_asset.id} was not ready within {timeout} seconds") + raise TimeoutError( + f"Data asset {data_asset.id} was not ready within {timeout} seconds" + ) - sleep(polling_interval) + sleep( + polling_interval + ) def delete_data_asset(self, data_asset_id: str): """Delete a data asset permanently.""" @@ -364,24 +780,37 @@ def update_permissions(self, data_asset_id: str, permissions: Permissions): ) def archive_data_asset(self, data_asset_id: str, archive: bool): - """Archive or unarchive a data asset to control its visibility and accessibility.""" + """ + Archive or unarchive a data asset to control its visibility and + accessibility. + """ self.client.patch( f"data_assets/{data_asset_id}/archive", params={"archive": archive}, ) - def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults: + def search_data_assets( + self, + search_params: DataAssetSearchParams + ) -> DataAssetSearchResults: """Search for data assets with filtering, sorting, and pagination options.""" res = self.client.post("data_assets/search", json=search_params.to_dict()) return DataAssetSearchResults.from_dict(res.json()) - def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> Iterator[DataAsset]: - """Iterate through all data assets matching search criteria with automatic pagination.""" + def search_data_assets_iterator( + self, search_params: DataAssetSearchParams + ) -> Iterator[DataAsset]: + """ + Iterate through all data assets matching search criteria with + automatic pagination. + """ params = search_params.to_dict() while True: response = self.search_data_assets( - search_params=DataAssetSearchParams(**params) + search_params=DataAssetSearchParams( + **params + ) ) for result in response.results: @@ -393,7 +822,10 @@ def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> I params["next_token"] = response.next_token def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder: - """List files and folders within an internal data asset at the specified path. Empty path retrieves root level contents.""" + """ + List files and folders within an internal data asset at the specified path. + Empty path retrieves root level contents. + """ data = { "path": path, } @@ -402,7 +834,9 @@ def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder: return Folder.from_dict(res.json()) - def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> DownloadFileURL: + def get_data_asset_file_download_url( + self, data_asset_id: str, path: str + ) -> DownloadFileURL: """Generate a download URL for a specific file from an internal data asset.""" res = self.client.get( f"data_assets/{data_asset_id}/files/download_url", @@ -411,15 +845,16 @@ def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> Dow return DownloadFileURL.from_dict(res.json()) - def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataParams): + def transfer_data_asset( + self, data_asset_id: str, transfer_params: TransferDataParams + ): """ Transfer a data asset's files to a different S3 storage location (Admin only). - - Can convert internal data assets to external or change storage location of external - data assets. Maintains provenance for result data assets. Use force=True when - transferring data assets used by release pipelines. + + Can convert internal data assets to external or change storage location of + external data assets. Maintains provenance for result data assets. Use + force=True when transferring data assets used by release pipelines. """ self.client.post( - f"data_assets/{data_asset_id}/transfer", - json=transfer_params.to_dict() + f"data_assets/{data_asset_id}/transfer", json=transfer_params.to_dict() ) From 95527494a20c2a64f21d48586670aaf69062dc74 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 13:20:49 +0300 Subject: [PATCH 11/19] line too long --- src/codeocean/computation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index d6c237a..d67042d 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -307,7 +307,8 @@ def wait_until_completed( sleep(polling_interval) def list_computation_results(self, computation_id: str, path: str = "") -> Folder: - """List result files and folders generated by a computation at the specified path. Empty path retrieves the /results root folder.""" + """List result files and folders generated by a computation + at the specified path. Empty path retrieves the /results root folder.""" data = { "path": path, } From 123c26664a12bde8cb0700f7e773feccee67c7c8 Mon Sep 17 00:00:00 2001 From: Dror Hilman Date: Thu, 12 Jun 2025 13:28:07 +0300 Subject: [PATCH 12/19] remove whitespace --- src/codeocean/computation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index d67042d..8da35ed 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -307,7 +307,7 @@ def wait_until_completed( sleep(polling_interval) def list_computation_results(self, computation_id: str, path: str = "") -> Folder: - """List result files and folders generated by a computation + """List result files and folders generated by a computation at the specified path. Empty path retrieves the /results root folder.""" data = { "path": path, From e883a7d97c38255815a7091d59f27f257d0d2935 Mon Sep 17 00:00:00 2001 From: Zvika Gart Date: Fri, 13 Jun 2025 13:40:38 +0100 Subject: [PATCH 13/19] formatting --- src/codeocean/capsule.py | 86 +++++------ src/codeocean/components.py | 39 +++-- src/codeocean/computation.py | 85 ++++++----- src/codeocean/data_asset.py | 274 ++++++++++++++++++----------------- src/codeocean/folder.py | 15 +- 5 files changed, 269 insertions(+), 230 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index ac6b605..ce7a997 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -33,16 +33,20 @@ class OriginalCapsuleInfo: another.""" id: Optional[str] = dataclass_field( - default=None, metadata={"description": "Original capsule ID"} + default=None, + metadata={"description": "Original capsule ID"}, ) major_version: Optional[int] = dataclass_field( - default=None, metadata={"description": "Original capsule major version"} + default=None, + metadata={"description": "Original capsule major version"}, ) minor_version: Optional[int] = dataclass_field( - default=None, metadata={"description": "Original capsule minor version"} + default=None, + metadata={"description": "Original capsule minor version"}, ) name: Optional[str] = dataclass_field( - default=None, metadata={"description": "Original capsule name"} + default=None, + metadata={"description": "Original capsule name"}, ) created: Optional[int] = dataclass_field( default=None, @@ -59,17 +63,23 @@ class OriginalCapsuleInfo: class Capsule: """Represents a Code Ocean capsule with its metadata and properties.""" - id: str = dataclass_field(metadata={"description": "Capsule ID"}) + id: str = dataclass_field( + metadata={"description": "Capsule ID"}, + ) created: int = dataclass_field( - metadata={"description": "Capsule creation time (int64 timestamp)"} + metadata={"description": "Capsule creation time (int64 timestamp)"}, + ) + name: str = dataclass_field( + metadata={"description": "Capsule display name"}, ) - name: str = dataclass_field(metadata={"description": "Capsule display name"}) status: CapsuleStatus = dataclass_field( - metadata={"description": "Status of the capsule (non_release or release)"} + metadata={"description": "Status of the capsule (non_release or release)"}, + ) + owner: str = dataclass_field( + metadata={"description": "Capsule owner's ID"}, ) - owner: str = dataclass_field(metadata={"description": "Capsule owner's ID"}) slug: str = dataclass_field( - metadata={"description": "Alternate capsule ID (URL-friendly identifier)"} + metadata={"description": "Alternate capsule ID (URL-friendly identifier)"}, ) article: Optional[dict] = dataclass_field( default=None, @@ -84,10 +94,12 @@ class Capsule: metadata={"description": "URL to external Git repository linked to capsule"}, ) description: Optional[str] = dataclass_field( - default=None, metadata={"description": "Capsule description"} + default=None, + metadata={"description": "Capsule description"}, ) field: Optional[str] = dataclass_field( - default=None, metadata={"description": "Capsule research field"} + default=None, + metadata={"description": "Capsule research field"}, ) tags: Optional[list[str]] = dataclass_field( default=None, @@ -96,12 +108,12 @@ class Capsule: original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field( default=None, metadata={ - "description": "Original capsule info when this is cloned from " - "another capsule" + "description": "Original capsule info when this is cloned from another capsule" }, ) release_capsule: Optional[str] = dataclass_field( - default=None, metadata={"description": "Release capsule ID"} + default=None, + metadata={"description": "Release capsule ID"}, ) submission: Optional[dict] = dataclass_field( default=None, @@ -114,8 +126,7 @@ class Capsule: versions: Optional[list[dict]] = dataclass_field( default=None, metadata={ - "description": "Capsule versions with major_version, " - "minor_version, release_time, and DOI" + "description": "Capsule versions with major_version, minor_version, release_time, and DOI" }, ) @@ -129,38 +140,35 @@ class CapsuleSearchParams: query: Optional[str] = dataclass_field( default=None, metadata={ - "description": "Search query in free text or structured format " - "(name:... tag:...)" + "description": "Search query in free text or structured format (name:... tag:...)" }, ) next_token: Optional[str] = dataclass_field( default=None, metadata={ - "description": "Token for next page of results from previous " "response" + "description": "Token for next page of results from previous response" }, ) offset: Optional[int] = dataclass_field( default=None, metadata={ - "description": "Starting index for search results (ignored if " - "next_token is set)" + "description": "Starting index for search results (ignored if next_token is set)" }, ) limit: Optional[int] = dataclass_field( default=None, metadata={ - "description": "Number of items to return (up to 1000, " "defaults to 100)" + "description": "Number of items to return (up to 1000, defaults to 100)" }, ) sort_field: Optional[CapsuleSortBy] = dataclass_field( default=None, - metadata={"description": "Field to sort by (created, name, " "last_accessed)"}, + metadata={"description": "Field to sort by (created, name, last_accessed)"}, ) sort_order: Optional[SortOrder] = dataclass_field( default=None, metadata={ - "description": "Sort order ('asc' or 'desc') - must be provided " - "with a sort_field parameter as well!" + "description": "Sort order ('asc' or 'desc') - must be provided with a sort_field parameter as well!" }, ) ownership: Optional[Ownership] = dataclass_field( @@ -171,22 +179,20 @@ class CapsuleSearchParams: ) status: Optional[CapsuleStatus] = dataclass_field( default=None, - metadata={ - "description": "Filter by status (release or non_release) - " - "defaults to all" - }, + metadata={"description": "Filter by status (release or non_release) - defaults to all"}, ) favorite: Optional[bool] = dataclass_field( - default=None, metadata={"description": "Search only favorite capsules"} + default=None, + metadata={"description": "Search only favorite capsules"}, ) archived: Optional[bool] = dataclass_field( - default=None, metadata={"description": "Search only archived capsules"} + default=None, + metadata={"description": "Search only archived capsules"}, ) filters: Optional[list[SearchFilter]] = dataclass_field( default=None, metadata={ - "description": "Additional field-level filters for name, " - "description, tags, or custom fields" + "description": "Additional field-level filters for name, description, tags, or custom fields" }, ) @@ -197,10 +203,10 @@ class CapsuleSearchResults: """Results from a capsule search operation with pagination support.""" has_more: bool = dataclass_field( - metadata={"description": "Indicates if there are more results available"} + metadata={"description": "Indicates if there are more results available"}, ) results: list[Capsule] = dataclass_field( - metadata={"description": "Array of capsules found matching the search criteria"} + metadata={"description": "Array of capsules found matching the search criteria"}, ) next_token: Optional[str] = dataclass_field( default=None, @@ -251,18 +257,14 @@ def detach_data_assets(self, capsule_id: str, data_assets: list[str]): json=data_assets, ) - def search_capsules( - self, search_params: CapsuleSearchParams - ) -> CapsuleSearchResults: + def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults: """Search for capsules with filtering, sorting, and pagination options.""" res = self.client.post("capsules/search", json=search_params.to_dict()) return CapsuleSearchResults.from_dict(res.json()) - def search_capsules_iterator( - self, search_params: CapsuleSearchParams - ) -> Iterator[Capsule]: + def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsule]: """Iterate through all capsules matching search criteria with automatic pagination.""" params = search_params.to_dict() while True: diff --git a/src/codeocean/components.py b/src/codeocean/components.py index f3c02f5..64ccb48 100644 --- a/src/codeocean/components.py +++ b/src/codeocean/components.py @@ -21,12 +21,12 @@ class UserPermissions: """User permission configuration with email and role assignment.""" email: str = field( - metadata={"description": "User email address for permission assignment"} + metadata={"description": "User email address for permission assignment"}, ) role: UserRole = field( metadata={ - "description": "Permission level granted to the user (owner, editor, viewer)" - } + "description": "Permission level granted to the user (owner, editor, viewer)", + }, ) @@ -45,12 +45,12 @@ class GroupPermissions: """Group permission configuration with group identifier and role assignment.""" group: str = field( - metadata={"description": "Group identifier for permission assignment"} + metadata={"description": "Group identifier for permission assignment"}, ) role: GroupRole = field( metadata={ - "description": "Permission level granted to the group (owner, editor, viewer, discoverable)" - } + "description": "Permission level granted to the group (owner, editor, viewer, discoverable)", + }, ) @@ -68,10 +68,12 @@ class Permissions: """Complete permission configuration for Code Ocean resources including users, groups, and public access.""" users: Optional[list[UserPermissions]] = field( - default=None, metadata={"description": "List of user-specific permissions"} + default=None, + metadata={"description": "List of user-specific permissions"}, ) groups: Optional[list[GroupPermissions]] = field( - default=None, metadata={"description": "List of group-specific permissions"} + default=None, + metadata={"description": "List of group-specific permissions"}, ) everyone: Optional[EveryoneRole] = field( default=None, @@ -80,7 +82,7 @@ class Permissions: share_assets: Optional[bool] = field( default=None, metadata={ - "description": "Whether to share associated assets with granted permissions" + "description": "Whether to share associated assets with granted permissions", }, ) @@ -97,8 +99,12 @@ class SortOrder(StrEnum): class SearchFilterRange: """Numeric range filter for search operations with minimum and maximum values.""" - min: float = field(metadata={"description": "Minimum value for range filter"}) - max: float = field(metadata={"description": "Maximum value for range filter"}) + min: float = field( + metadata={"description": "Minimum value for range filter"}, + ) + max: float = field( + metadata={"description": "Maximum value for range filter"}, + ) @dataclass_json @@ -108,11 +114,12 @@ class SearchFilter: key: str = field( metadata={ - "description": "Field name to filter on (name, description, tags, or custom field key)" - } + "description": "Field name to filter on (name, description, tags, or custom field key)", + }, ) value: Optional[str | float] = field( - default=None, metadata={"description": "Single field value to include/exclude"} + default=None, + metadata={"description": "Single field value to include/exclude"}, ) values: Optional[list[str | float]] = field( default=None, @@ -121,13 +128,13 @@ class SearchFilter: range: Optional[SearchFilterRange] = field( default=None, metadata={ - "description": "Numeric range filter (only one of min/max must be set)" + "description": "Numeric range filter (only one of min/max must be set)", }, ) exclude: Optional[bool] = field( default=None, metadata={ - "description": "Whether to include (false) or exclude (true) the specified values" + "description": "Whether to include (false) or exclude (true) the specified values", }, ) diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index 8da35ed..18f0c90 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -34,13 +34,16 @@ class Param: """Parameter information for computations with name and value.""" name: Optional[str] = field( - default=None, metadata={"description": "Parameter label/display name"} + default=None, + metadata={"description": "Parameter label/display name"}, ) param_name: Optional[str] = field( - default=None, metadata={"description": "Internal parameter name identifier"} + default=None, + metadata={"description": "Internal parameter name identifier"}, ) value: Optional[str] = field( - default=None, metadata={"description": "Parameter value as string"} + default=None, + metadata={"description": "Parameter value as string"}, ) @@ -50,10 +53,10 @@ class PipelineProcess: """Information about a process within a pipeline execution.""" name: str = field( - metadata={"description": "Pipeline process name as it appears in main.nf"} + metadata={"description": "Pipeline process name as it appears in main.nf"}, ) capsule_id: str = field( - metadata={"description": "ID of the capsule executed in this process"} + metadata={"description": "ID of the capsule executed in this process"}, ) version: Optional[int] = field( default=None, @@ -64,7 +67,8 @@ class PipelineProcess: metadata={"description": "Indicates if the capsule is a Code Ocean public app"}, ) parameters: Optional[list[Param]] = field( - default=None, metadata={"description": "Run parameters for this process"} + default=None, + metadata={"description": "Run parameters for this process"}, ) @@ -73,9 +77,12 @@ class PipelineProcess: class InputDataAsset: """Data asset attached to a computation with mount information.""" - id: str = field(metadata={"description": "Attached data asset ID"}) + id: str = field( + metadata={"description": "Attached data asset ID"}, + ) mount: Optional[str] = field( - default=None, metadata={"description": "Mount path for the attached data asset"} + default=None, + metadata={"description": "Mount path for the attached data asset"}, ) @@ -84,21 +91,27 @@ class InputDataAsset: class Computation: """Represents a Code Ocean computation run with its metadata and execution details.""" - id: str = field(metadata={"description": "Unique computation ID"}) + id: str = field( + metadata={"description": "Unique computation ID"}, + ) created: int = field( - metadata={"description": "Computation creation time (int64 timestamp)"} + metadata={"description": "Computation creation time (int64 timestamp)"}, + ) + name: str = field( + metadata={"description": "Display name of the computation"}, + ) + run_time: int = field( + metadata={"description": "Total run time in seconds"}, ) - name: str = field(metadata={"description": "Display name of the computation"}) - run_time: int = field(metadata={"description": "Total run time in seconds"}) state: ComputationState = field( metadata={ - "description": "Current state of the computation (initializing, running, finalizing, completed, failed)" - } + "description": "Current state of the computation (initializing, running, finalizing, completed, failed)", + }, ) cloud_workstation: Optional[bool] = field( default=None, metadata={ - "description": "Indicates whether this computation is a cloud workstation" + "description": "Indicates whether this computation is a cloud workstation", }, ) data_assets: Optional[list[InputDataAsset]] = field( @@ -116,25 +129,25 @@ class Computation: processes: Optional[list[PipelineProcess]] = field( default=None, metadata={ - "description": "Pipeline processes information if this is a pipeline computation" + "description": "Pipeline processes information if this is a pipeline computation", }, ) end_status: Optional[ComputationEndStatus] = field( default=None, metadata={ - "description": "Final status once computation is completed (succeeded, failed, stopped)" + "description": "Final status once computation is completed (succeeded, failed, stopped)", }, ) exit_code: Optional[int] = field( default=None, metadata={ - "description": "Exit code (0 for success, non-zero for failure, 1 for pipeline errors)" + "description": "Exit code (0 for success, non-zero for failure, 1 for pipeline errors)", }, ) has_results: Optional[bool] = field( default=None, metadata={ - "description": "Indicates whether the computation has generated results" + "description": "Indicates whether the computation has generated results", }, ) nextflow_profile: Optional[str] = field( @@ -148,7 +161,9 @@ class Computation: class DataAssetsRunParam: """Data asset parameter for running computations with mount specification.""" - id: str = field(metadata={"description": "Data asset ID to attach"}) + id: str = field( + metadata={"description": "Data asset ID to attach"}, + ) mount: Optional[str] = field( default=None, metadata={"description": "Mount path where the data asset will be accessible"}, @@ -161,9 +176,11 @@ class NamedRunParam: """Named parameter for running computations with explicit parameter name.""" param_name: str = field( - metadata={"description": "Internal parameter name identifier"} + metadata={"description": "Internal parameter name identifier"}, + ) + value: str = field( + metadata={"description": "Parameter value as string"}, ) - value: str = field(metadata={"description": "Parameter value as string"}) @dataclass_json @@ -172,14 +189,15 @@ class PipelineProcessParams: """Parameters for configuring a specific process within a pipeline execution.""" name: str = field( - metadata={"description": "Name of the pipeline process to configure"} + metadata={"description": "Name of the pipeline process to configure"}, ) parameters: Optional[list[str]] = field( default=None, metadata={"description": "Ordered list of parameter values for this process"}, ) named_parameters: Optional[list[NamedRunParam]] = field( - default=None, metadata={"description": "Named parameters for this process"} + default=None, + metadata={"description": "Named parameters for this process"}, ) @@ -191,17 +209,18 @@ class RunParams: capsule_id: Optional[str] = field( default=None, metadata={ - "description": "ID of the capsule to run (required for capsule runs)" + "description": "ID of the capsule to run (required for capsule runs)", }, ) pipeline_id: Optional[str] = field( default=None, metadata={ - "description": "ID of the pipeline to run (required for pipeline runs)" + "description": "ID of the pipeline to run (required for pipeline runs)", }, ) version: Optional[int] = field( - default=None, metadata={"description": "Specific version of the capsule to run"} + default=None, + metadata={"description": "Specific version of the capsule to run"}, ) resume_run_id: Optional[str] = field( default=None, @@ -214,14 +233,16 @@ class RunParams: data_assets: Optional[list[DataAssetsRunParam]] = field( default=None, metadata={ - "description": "List of data assets to attach with their mount paths" + "description": "List of data assets to attach with their mount paths", }, ) parameters: Optional[list[str]] = field( - default=None, metadata={"description": "Ordered list of parameter values"} + default=None, + metadata={"description": "Ordered list of parameter values"}, ) named_parameters: Optional[list[NamedRunParam]] = field( - default=None, metadata={"description": "Named parameters for the computation"} + default=None, + metadata={"description": "Named parameters for the computation"}, ) processes: Optional[list[PipelineProcessParams]] = field( default=None, @@ -317,9 +338,7 @@ def list_computation_results(self, computation_id: str, path: str = "") -> Folde return Folder.from_dict(res.json()) - def get_result_file_download_url( - self, computation_id: str, path: str - ) -> DownloadFileURL: + def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL: """Generate a download URL for a specific result file from a computation.""" res = self.client.get( f"computations/{computation_id}/results/download_url", diff --git a/src/codeocean/data_asset.py b/src/codeocean/data_asset.py index a5e9243..9b3b514 100644 --- a/src/codeocean/data_asset.py +++ b/src/codeocean/data_asset.py @@ -47,12 +47,13 @@ class Provenance: metadata={"description": "Docker image used to create the data asset"}, ) capsule: Optional[str] = field( - default=None, metadata={"description": "Capsule used to create the data asset"} + default=None, + metadata={"description": "Capsule used to create the data asset"}, ) data_assets: Optional[list[str]] = field( default=None, metadata={ - "description": "Data assets that were used to create this data asset" + "description": "Data assets that were used to create this data asset", }, ) computation: Optional[str] = field( @@ -75,18 +76,16 @@ class SourceBucket: """Information about the bucket from which the data asset was created.""" origin: DataAssetOrigin = field( - metadata={"description": "Origin type (aws, local, gcp)"} + metadata={"description": "Origin type (aws, local, gcp)"}, ) bucket: Optional[str] = field( - default=None, metadata={"description": "The original bucket's name"} + default=None, + metadata={"description": "The original bucket's name"}, ) prefix: Optional[str] = field( default=None, metadata={ - "description": ( - "The folder in the S3 bucket from which " - "the data asset was created" - ) + "description": "The folder in the S3 bucket from which the data asset was created", }, ) external: Optional[bool] = field( @@ -101,10 +100,12 @@ class AppParameter: """Name and value of app panel parameters used to generate result data assets.""" name: Optional[str] = field( - default=None, metadata={"description": "Parameter name"} + default=None, + metadata={"description": "Parameter name"}, ) value: Optional[str] = field( - default=None, metadata={"description": "Parameter value"} + default=None, + metadata={"description": "Parameter value"}, ) @@ -117,43 +118,44 @@ class ResultsInfo: """ capsule_id: Optional[str] = field( - default=None, metadata={"description": "ID of the capsule that was executed"} + default=None, + metadata={"description": "ID of the capsule that was executed"}, ) pipeline_id: Optional[str] = field( - default=None, metadata={"description": "ID of the pipeline that was executed"} + default=None, + metadata={"description": "ID of the pipeline that was executed"}, ) version: Optional[int] = field( - default=None, metadata={"description": "Capsule or pipeline release version"} + default=None, + metadata={"description": "Capsule or pipeline release version"}, ) commit: Optional[str] = field( default=None, metadata={ - "description": "Commit hash of capsule/pipeline code at time of execution" + "description": "Commit hash of capsule/pipeline code at time of execution", }, ) run_script: Optional[str] = field( default=None, metadata={ - "description": "Path to the script that was executed relative to" - " /Capsule folder" + "description": "Path to the script that was executed relative to /capsule folder", }, ) data_assets: Optional[list[str]] = field( default=None, - metadata={"description": "IDs of data assets used during the run"} + metadata={"description": "IDs of data assets used during the run"}, ) parameters: Optional[list[Param]] = field( default=None, - metadata={ - "description": "Run parameters used for execution" - } + metadata={"description": "Run parameters used for execution"}, ) nextflow_profile: Optional[str] = field( default=None, metadata={"description": "Pipeline Nextflow profile"}, ) processes: Optional[list[PipelineProcess]] = field( - default=None, metadata={"description": "Pipeline processes information"} + default=None, + metadata={"description": "Pipeline processes information"}, ) @@ -162,65 +164,72 @@ class ResultsInfo: class DataAsset: """Represents a Code Ocean data asset with its metadata and properties.""" - id: str = field(metadata={"description": "Unique data asset ID (UUID string)"}) + id: str = field( + metadata={"description": "Unique data asset ID (UUID string)"}, + ) created: int = field( - metadata={"description": "Data asset creation time (seconds since epoch)"} + metadata={"description": "Data asset creation time (seconds since epoch)"}, + ) + name: str = field( + metadata={"description": "Name of the data asset"}, ) - name: str = field(metadata={"description": "Name of the data asset"}) mount: str = field( - metadata={"description": "The default mount folder of the data asset"} + metadata={"description": "The default mount folder of the data asset"}, ) state: DataAssetState = field( - metadata={"description": "Data asset creation state (draft, ready, failed)"} + metadata={"description": "Data asset creation state (draft, ready, failed)"}, ) type: DataAssetType = field( metadata={ - "description": "Type of the data asset (dataset, result, combined, model)" - } + "description": "Type of the data asset (dataset, result, combined, model)", + }, ) last_used: int = field( metadata={ - "description": "Time data asset was last used in seconds from unix epoch" - } + "description": "Time data asset was last used in seconds from unix epoch", + }, ) files: Optional[int] = field( - default=None, metadata={"description": "Number of files in the data asset"} + default=None, + metadata={"description": "Number of files in the data asset"}, ) size: Optional[int] = field( default=None, metadata={"description": "Size in bytes of the data asset (parsed to int)"}, ) description: Optional[str] = field( - default=None, metadata={"description": "Data asset description"} + default=None, + metadata={"description": "Data asset description"}, ) tags: Optional[list[str]] = field( - default=None, metadata={"description": "Keywords for searching the data asset"} + default=None, + metadata={"description": "Keywords for searching the data asset"}, ) provenance: Optional[Provenance] = field( default=None, metadata={ "description": "Shows the data asset provenance if type is result; only " - "'capsule' or 'computation' field will be populated depending on source" + "'capsule' or 'computation' field will be populated depending on source", }, ) source_bucket: Optional[SourceBucket] = field( default=None, metadata={ - "description": "Information on bucket from which data asset was created" + "description": "Information on bucket from which data asset was created", }, ) custom_metadata: Optional[dict] = field( default=None, metadata={ "description": "Custom metadata fields defined by deployment admin with " - "user-set values" + "user-set values", }, ) app_parameters: Optional[list[AppParameter]] = field( default=None, metadata={ "description": "Name and value of app panel parameters used to generate " - "result data asset" + "result data asset", }, ) nextflow_profile: Optional[str] = field( @@ -235,20 +244,20 @@ class DataAsset: default=None, metadata={ "description": "Time data asset's files were last transferred to a " - "different S3 storage location" + "different S3 storage location", }, ) transfer_error: Optional[str] = field( default=None, metadata={ "description": "The error that occurred during the last transfer attempt " - "if it failed" + "if it failed", }, ) failure_reason: Optional[str] = field( default=None, metadata={ - "description": "Reason for data asset creation failure if state is failed" + "description": "Reason for data asset creation failure if state is failed", }, ) @@ -259,16 +268,20 @@ class DataAssetUpdateParams: """Parameters for updating data asset metadata.""" name: Optional[str] = field( - default=None, metadata={"description": "Data asset name"} + default=None, + metadata={"description": "Data asset name"}, ) description: Optional[str] = field( - default=None, metadata={"description": "Data asset description"} + default=None, + metadata={"description": "Data asset description"}, ) tags: Optional[list[str]] = field( - default=None, metadata={"description": "Keywords for searching the data asset"} + default=None, + metadata={"description": "Keywords for searching the data asset"}, ) mount: Optional[str] = field( - default=None, metadata={"description": "Default mount folder of the data asset"} + default=None, + metadata={"description": "Default mount folder of the data asset"}, ) custom_metadata: Optional[dict] = field( default=None, @@ -283,34 +296,25 @@ class AWSS3Source: bucket: str = field( metadata={ - "description": "The S3 bucket from which the data asset will be created" - } + "description": "The S3 bucket from which the data asset will be created", + }, ) prefix: Optional[str] = field( default=None, metadata={ - "description": ( - "The folder in the S3 bucket from which " - "the data asset will be created" - ) + "description": "The folder in the S3 bucket from which the data asset will be created", }, ) keep_on_external_storage: Optional[bool] = field( default=None, metadata={ - "description": ( - "When true, data asset files will not be copied to " - "Code Ocean" - ) + "description": "When true, data asset files will not be copied to Code Ocean", }, ) public: Optional[bool] = field( default=None, metadata={ - "description": ( - "When true, Code Ocean will access the source bucket " - "without credentials" - ) + "description": "When true, Code Ocean will access the source bucket without credentials", }, ) @@ -325,23 +329,23 @@ class GCPCloudStorageSource: bucket: str = field( metadata={ - "description": "The GCP Cloud Storage bucket from which the" - " data asset will be created" - } + "description": "The GCP Cloud Storage bucket from which the data asset will be created", + }, ) client_id: Optional[str] = field( - default=None, metadata={"description": "GCP client ID for authentication"} + default=None, + metadata={"description": "GCP client ID for authentication"}, ) client_secret: Optional[str] = field( - default=None, metadata={"description": "GCP client secret for authentication"} + default=None, + metadata={"description": "GCP client secret for authentication"}, ) prefix: Optional[str] = field( default=None, metadata={ "description": ( - "The folder in the GCP bucket from which " - "the data asset will be created" - ) + "The folder in the GCP bucket from which the data asset will be created" + ), }, ) @@ -352,15 +356,14 @@ class ComputationSource: """Computation source configuration for creating result data assets.""" id: str = field( - metadata={"description": "Computation ID from which to create the data asset"} + metadata={"description": "Computation ID from which to create the data asset"}, ) path: Optional[str] = field( default=None, metadata={ "description": ( - "Results path within computation " - "(empty captures all result files)" - ) + "Results path within computation (empty captures all result files)" + ), }, ) @@ -371,13 +374,16 @@ class Source: """Source configuration for data asset creation from various origins.""" aws: Optional[AWSS3Source] = field( - default=None, metadata={"description": "AWS S3 source configuration"} + default=None, + metadata={"description": "AWS S3 source configuration"}, ) gcp: Optional[GCPCloudStorageSource] = field( - default=None, metadata={"description": "GCP Cloud Storage source configuration"} + default=None, + metadata={"description": "GCP Cloud Storage source configuration"}, ) computation: Optional[ComputationSource] = field( - default=None, metadata={"description": "Computation source configuration"} + default=None, + metadata={"description": "Computation source configuration"}, ) @@ -387,15 +393,14 @@ class AWSS3Target: """AWS S3 target configuration for external data asset storage.""" bucket: str = field( - metadata={"description": "The S3 bucket where the data asset will be stored"} + metadata={"description": "The S3 bucket where the data asset will be stored"}, ) prefix: Optional[str] = field( default=None, metadata={ "description": ( - "The folder in the S3 bucket where the data asset " - "will be placed" - ) + "The folder in the S3 bucket where the data asset will be placed" + ), }, ) @@ -406,7 +411,8 @@ class Target: """Target configuration for external data asset storage.""" aws: Optional[AWSS3Target] = field( - default=None, metadata={"description": "AWS S3 target configuration"} + default=None, + metadata={"description": "AWS S3 target configuration"}, ) @@ -418,15 +424,20 @@ class DataAssetParams: and configurations. """ - name: str = field(metadata={"description": "Data asset name"}) + name: str = field( + metadata={"description": "Data asset name"}, + ) tags: list[str] = field( metadata={ - "description": "Keywords applied to the data asset to aid in searching" - } + "description": "Keywords applied to the data asset to aid in searching", + }, + ) + mount: str = field( + metadata={"description": "Data asset default mount folder"}, ) - mount: str = field(metadata={"description": "Data asset default mount folder"}) description: Optional[str] = field( - default=None, metadata={"description": "Data asset description"} + default=None, + metadata={"description": "Data asset description"}, ) source: Optional[Source] = field( default=None, @@ -439,20 +450,19 @@ class DataAssetParams: custom_metadata: Optional[dict] = field( default=None, metadata={ - "description": "Custom metadata fields according to admin-defined fields" + "description": "Custom metadata fields according to admin-defined fields", }, ) data_asset_ids: Optional[list[str]] = field( default=None, metadata={ - "description": "List of data asset IDs for creating combined data assets" + "description": "List of data asset IDs for creating combined data assets", }, ) results_info: Optional[ResultsInfo] = field( default=None, metadata={ - "description": "Additional information for " - "data assets from external results" + "description": "Additional information for data assets from external results", }, ) @@ -462,9 +472,12 @@ class DataAssetParams: class DataAssetAttachParams: """Parameters for attaching data assets to capsules.""" - id: str = field(metadata={"description": "Data asset ID to attach"}) + id: str = field( + metadata={"description": "Data asset ID to attach"}, + ) mount: Optional[str] = field( - default=None, metadata={"description": "Mount path for the attached data asset"} + default=None, + metadata={"description": "Mount path for the attached data asset"}, ) @@ -473,12 +486,16 @@ class DataAssetAttachParams: class DataAssetAttachResults: """Results from attaching data assets to capsules.""" - id: str = field(metadata={"description": "Data asset ID that was attached"}) + id: str = field( + metadata={"description": "Data asset ID that was attached"}, + ) mount_state: Optional[str] = field( - default=None, metadata={"description": "Current state of the data asset mount"} + default=None, + metadata={"description": "Current state of the data asset mount"}, ) job_id: Optional[str] = field( - default=None, metadata={"description": "Job ID for the attachment operation"} + default=None, + metadata={"description": "Job ID for the attachment operation"}, ) external: Optional[bool] = field( default=None, @@ -522,28 +539,27 @@ class DataAssetSearchParams: default=None, metadata={ "description": "Search query in free text or structured format " - "(name:... tag:... run_script:... commit_id:...)" + "(name:... tag:... run_script:... commit_id:...)", }, ) next_token: Optional[str] = field( default=None, metadata={ - "description": "Token for next page of results from previous response" + "description": "Token for next page of results from previous response", }, ) offset: Optional[int] = field( default=None, metadata={ "description": ( - "Starting index for search results " - "(ignored if next_token is set)" - ) + "Starting index for search results (ignored if next_token is set)" + ), }, ) limit: Optional[int] = field( default=None, metadata={ - "description": "Number of items to return (up to 1000, defaults to 100)" + "description": "Number of items to return (up to 1000, defaults to 100)", }, ) sort_field: Optional[DataAssetSortBy] = field( @@ -553,23 +569,22 @@ class DataAssetSearchParams: sort_order: Optional[SortOrder] = field( default=None, metadata={ - "description": "Sort order (asc or desc) - must be provided with sort_field" + "description": "Sort order (asc or desc) - must be provided with sort_field", }, ) type: Optional[DataAssetType] = field( default=None, metadata={ "description": "Filter by data asset type " - "(dataset, result, combined, model)" + "(dataset, result, combined, model)", }, ) ownership: Optional[Ownership] = field( default=None, metadata={ "description": ( - "Filter by ownership (created or shared) - " - "defaults to all accessible" - ) + "Filter by ownership (created or shared) - defaults to all accessible" + ), }, ) origin: Optional[DataAssetSearchOrigin] = field( @@ -577,17 +592,17 @@ class DataAssetSearchParams: metadata={"description": "Filter by origin (internal or external)"}, ) favorite: Optional[bool] = field( - default=None, metadata={"description": "Search only favorite data assets"} + default=None, + metadata={"description": "Search only favorite data assets"}, ) archived: Optional[bool] = field( - default=None, metadata={"description": "Search only archived data assets"} + default=None, + metadata={"description": "Search only archived data assets"}, ) filters: Optional[list[SearchFilter]] = field( default=None, metadata={ - "description": "Additional field-level filters " - "for name, description, tags, " - "or custom fields" + "description": "Additional field-level filters for name, description, tags, or custom fields", }, ) @@ -599,15 +614,15 @@ class DataAssetSearchResults: has_more: bool = field( metadata={ - "description": "Indicates if there are more results available" - } + "description": "Indicates if there are more results available", + }, ) results: list[DataAsset] = field( metadata={ "description": ( "Array of data assets found matching the search criteria" - ) - } + ), + }, ) next_token: Optional[str] = field( default=None, @@ -621,11 +636,12 @@ class ContainedDataAsset: """Information about data assets contained within a combined data asset.""" id: Optional[str] = field( - default=None, metadata={"description": "ID of the contained data asset"} + default=None, + metadata={"description": "ID of the contained data asset"}, ) mount: Optional[str] = field( default=None, - metadata={"description": "Mount path of the contained data asset"} + metadata={"description": "Mount path of the contained data asset"}, ) size: Optional[int] = field( default=None, @@ -641,16 +657,15 @@ class TransferDataParams: target: Target = field( metadata={ - "description": "Target storage location configuration" - } + "description": "Target storage location configuration", + }, ) force: Optional[bool] = field( default=None, metadata={ "description": ( - "Perform transfer even if there are release pipelines " - "using the data asset" - ) + "Perform transfer even if there are release pipelines using the data asset" + ), }, ) @@ -667,9 +682,7 @@ def get_data_asset(self, data_asset_id: str) -> DataAsset: return DataAsset.from_dict(res.json()) - def update_metadata( - self, data_asset_id: str, update_params: DataAssetUpdateParams - ) -> DataAsset: + def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset: """ Update metadata for a data asset including name, description, tags, mount, and custom metadata. @@ -789,18 +802,13 @@ def archive_data_asset(self, data_asset_id: str, archive: bool): params={"archive": archive}, ) - def search_data_assets( - self, - search_params: DataAssetSearchParams - ) -> DataAssetSearchResults: + def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults: """Search for data assets with filtering, sorting, and pagination options.""" res = self.client.post("data_assets/search", json=search_params.to_dict()) return DataAssetSearchResults.from_dict(res.json()) - def search_data_assets_iterator( - self, search_params: DataAssetSearchParams - ) -> Iterator[DataAsset]: + def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> Iterator[DataAsset]: """ Iterate through all data assets matching search criteria with automatic pagination. @@ -834,9 +842,7 @@ def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder: return Folder.from_dict(res.json()) - def get_data_asset_file_download_url( - self, data_asset_id: str, path: str - ) -> DownloadFileURL: + def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> DownloadFileURL: """Generate a download URL for a specific file from an internal data asset.""" res = self.client.get( f"data_assets/{data_asset_id}/files/download_url", @@ -845,9 +851,7 @@ def get_data_asset_file_download_url( return DownloadFileURL.from_dict(res.json()) - def transfer_data_asset( - self, data_asset_id: str, transfer_params: TransferDataParams - ): + def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataParams): """ Transfer a data asset's files to a different S3 storage location (Admin only). diff --git a/src/codeocean/folder.py b/src/codeocean/folder.py index 954e2cc..c7eac31 100644 --- a/src/codeocean/folder.py +++ b/src/codeocean/folder.py @@ -10,11 +10,18 @@ class FolderItem: """Represents a file or folder item within a folder listing.""" - name: str = field(metadata={"description": "Name of the file or folder"}) - path: str = field(metadata={"description": "Path of the file or folder"}) - type: str = field(metadata={"description": "Item type ('file' or 'folder')"}) + name: str = field( + metadata={"description": "Name of the file or folder"} + ) + path: str = field( + metadata={"description": "Path of the file or folder"} + ) + type: str = field( + metadata={"description": "Item type ('file' or 'folder')"} + ) size: Optional[int] = field( - default=None, metadata={"description": "Size in bytes (only for files)"} + default=None, + metadata={"description": "Size in bytes (only for files)"} ) From 3e1271e7da210eefaaf36e26608a1e9288b781f7 Mon Sep 17 00:00:00 2001 From: Zvika Gart Date: Fri, 13 Jun 2025 13:56:54 +0100 Subject: [PATCH 14/19] fix --- src/codeocean/computation.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index 18f0c90..4a68b28 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -150,10 +150,6 @@ class Computation: "description": "Indicates whether the computation has generated results", }, ) - nextflow_profile: Optional[str] = field( - default=None, - metadata={"description": "Pipeline Nextflow profile used for this computation"}, - ) @dataclass_json From 8c284c640be1992b6df199e4e34c014a534ec39e Mon Sep 17 00:00:00 2001 From: Zvika Gart Date: Fri, 13 Jun 2025 14:38:54 +0100 Subject: [PATCH 15/19] formatting --- src/codeocean/capsule.py | 4 +--- src/codeocean/client.py | 1 + src/codeocean/data_asset.py | 27 +++++++++------------------ 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index ce7a997..05978a0 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -239,9 +239,7 @@ def update_permissions(self, capsule_id: str, permissions: Permissions): json=permissions.to_dict(), ) - def attach_data_assets( - self, capsule_id: str, attach_params: list[DataAssetAttachParams] - ) -> list[DataAssetAttachResults]: + def attach_data_assets(self, capsule_id: str, attach_params: list[DataAssetAttachParams]) -> list[DataAssetAttachResults]: """Attach one or more data assets to a capsule with optional mount paths.""" res = self.client.post( f"capsules/{capsule_id}/data_assets", diff --git a/src/codeocean/client.py b/src/codeocean/client.py index 73ad68c..ef15638 100644 --- a/src/codeocean/client.py +++ b/src/codeocean/client.py @@ -13,6 +13,7 @@ @dataclass class CodeOcean: + domain: str token: str retries: Optional[Retry | int] = 0 diff --git a/src/codeocean/data_asset.py b/src/codeocean/data_asset.py index 9b3b514..e423d52 100644 --- a/src/codeocean/data_asset.py +++ b/src/codeocean/data_asset.py @@ -696,7 +696,7 @@ def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdatePara """ res = self.client.put( f"data_assets/{data_asset_id}", - json=update_params.to_dict() + json=update_params.to_dict(), ) return DataAsset.from_dict(res.json()) @@ -752,18 +752,15 @@ def wait_until_ready( """ if polling_interval < 5: raise ValueError( - f"Polling interval {polling_interval} " - "should be greater than or equal to 5" + f"Polling interval {polling_interval} should be greater than or equal to 5" ) if timeout is not None and timeout < polling_interval: raise ValueError( - f"Timeout {timeout} should be greater than or equal to " - f"polling interval {polling_interval}" + f"Timeout {timeout} should be greater than or equal to polling interval {polling_interval}" ) if timeout is not None and timeout < 0: raise ValueError( - f"Timeout {timeout} should be greater than or equal to" - " 0 (seconds), or None" + f"Timeout {timeout} should be greater than or equal to 0 (seconds), or None" ) t0 = time() while True: @@ -777,9 +774,7 @@ def wait_until_ready( f"Data asset {data_asset.id} was not ready within {timeout} seconds" ) - sleep( - polling_interval - ) + sleep(polling_interval) def delete_data_asset(self, data_asset_id: str): """Delete a data asset permanently.""" @@ -793,10 +788,7 @@ def update_permissions(self, data_asset_id: str, permissions: Permissions): ) def archive_data_asset(self, data_asset_id: str, archive: bool): - """ - Archive or unarchive a data asset to control its visibility and - accessibility. - """ + """Archive or unarchive a data asset to control its visibility and accessibility.""" self.client.patch( f"data_assets/{data_asset_id}/archive", params={"archive": archive}, @@ -816,9 +808,7 @@ def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> I params = search_params.to_dict() while True: response = self.search_data_assets( - search_params=DataAssetSearchParams( - **params - ) + search_params=DataAssetSearchParams(**params), ) for result in response.results: @@ -860,5 +850,6 @@ def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataP force=True when transferring data assets used by release pipelines. """ self.client.post( - f"data_assets/{data_asset_id}/transfer", json=transfer_params.to_dict() + f"data_assets/{data_asset_id}/transfer", + json=transfer_params.to_dict(), ) From d4f04381ab4135a85fafb85ba9d19c3147375460 Mon Sep 17 00:00:00 2001 From: Zvika Gart Date: Fri, 13 Jun 2025 14:40:56 +0100 Subject: [PATCH 16/19] lint --- src/codeocean/capsule.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index 05978a0..859cb75 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -239,7 +239,11 @@ def update_permissions(self, capsule_id: str, permissions: Permissions): json=permissions.to_dict(), ) - def attach_data_assets(self, capsule_id: str, attach_params: list[DataAssetAttachParams]) -> list[DataAssetAttachResults]: + def attach_data_assets( + self, + capsule_id: str, + attach_params: list[DataAssetAttachParams], + ) -> list[DataAssetAttachResults]: """Attach one or more data assets to a capsule with optional mount paths.""" res = self.client.post( f"capsules/{capsule_id}/data_assets", From 29c2567fd7ecd2b50492f02403ec10fddd4f3138 Mon Sep 17 00:00:00 2001 From: jake-valsamis Date: Mon, 23 Jun 2025 18:55:39 -0400 Subject: [PATCH 17/19] clarifications to descriptions in capsule.py and components.py --- src/codeocean/capsule.py | 6 +++--- src/codeocean/components.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/codeocean/capsule.py b/src/codeocean/capsule.py index 859cb75..119afcc 100644 --- a/src/codeocean/capsule.py +++ b/src/codeocean/capsule.py @@ -29,7 +29,7 @@ class CapsuleSortBy(StrEnum): @dataclass_json @dataclass(frozen=True) class OriginalCapsuleInfo: - """Information about the original capsule when this capsule is cloned from + """Information about the original capsule when this capsule is duplicated from another.""" id: Optional[str] = dataclass_field( @@ -108,7 +108,7 @@ class Capsule: original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field( default=None, metadata={ - "description": "Original capsule info when this is cloned from another capsule" + "description": "Original capsule info when this capsule is duplicated from another" }, ) release_capsule: Optional[str] = dataclass_field( @@ -163,7 +163,7 @@ class CapsuleSearchParams: ) sort_field: Optional[CapsuleSortBy] = dataclass_field( default=None, - metadata={"description": "Field to sort by (created, name, last_accessed)"}, + metadata={"description": "Field to sort by (created, name, or last_accessed)"}, ) sort_order: Optional[SortOrder] = dataclass_field( default=None, diff --git a/src/codeocean/components.py b/src/codeocean/components.py index 64ccb48..c0d351d 100644 --- a/src/codeocean/components.py +++ b/src/codeocean/components.py @@ -8,7 +8,7 @@ class UserRole(StrEnum): - """Role levels for user permissions in Code Ocean resources.""" + """Role levels for user permissions of Code Ocean resources.""" Owner = "owner" Editor = "editor" @@ -25,13 +25,13 @@ class UserPermissions: ) role: UserRole = field( metadata={ - "description": "Permission level granted to the user (owner, editor, viewer)", + "description": "Permission level granted to the user (owner, editor, or viewer)", }, ) class GroupRole(StrEnum): - """Role levels for group permissions in Code Ocean resources.""" + """Role levels for group permissions of Code Ocean resources.""" Owner = "owner" Editor = "editor" @@ -49,13 +49,13 @@ class GroupPermissions: ) role: GroupRole = field( metadata={ - "description": "Permission level granted to the group (owner, editor, viewer, discoverable)", + "description": "Permission level granted to the group (owner, editor, viewer, or discoverable)", }, ) class EveryoneRole(StrEnum): - """Role levels for public access permissions in Code Ocean resources.""" + """Role levels for public access permissions of Code Ocean resources.""" Viewer = "viewer" Discoverable = "discoverable" @@ -77,18 +77,18 @@ class Permissions: ) everyone: Optional[EveryoneRole] = field( default=None, - metadata={"description": "Public access level (viewer, discoverable, none)"}, + metadata={"description": "Public access level (viewer, discoverable, or none)"}, ) share_assets: Optional[bool] = field( default=None, metadata={ - "description": "Whether to share associated assets with granted permissions", + "description": "Whether to share all related assets (attached data assets and pipeline capsules) with added users and groups", }, ) class SortOrder(StrEnum): - """Sort order options for search and listing operations.""" + """Sort order options for search operations.""" Ascending = "asc" Descending = "desc" From cf046d5c9120a80103563ad50f052523bdde31a7 Mon Sep 17 00:00:00 2001 From: jake-valsamis Date: Mon, 23 Jun 2025 19:07:34 -0400 Subject: [PATCH 18/19] fix line too long formatting --- src/codeocean/components.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/codeocean/components.py b/src/codeocean/components.py index c0d351d..f580d64 100644 --- a/src/codeocean/components.py +++ b/src/codeocean/components.py @@ -82,7 +82,8 @@ class Permissions: share_assets: Optional[bool] = field( default=None, metadata={ - "description": "Whether to share all related assets (attached data assets and pipeline capsules) with added users and groups", + "description": "Whether to share all related assets (attached data assets and " + "pipeline capsules) with added users and groups", }, ) From 02a9043667f0d16367fd0d77afca98a371023667 Mon Sep 17 00:00:00 2001 From: jake-valsamis Date: Tue, 24 Jun 2025 17:37:04 -0400 Subject: [PATCH 19/19] clarifications for metadata in computation.py and data_asset.py --- src/codeocean/computation.py | 9 +++------ src/codeocean/data_asset.py | 22 ++++++++-------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/codeocean/computation.py b/src/codeocean/computation.py index 4a68b28..53c769b 100644 --- a/src/codeocean/computation.py +++ b/src/codeocean/computation.py @@ -185,7 +185,7 @@ class PipelineProcessParams: """Parameters for configuring a specific process within a pipeline execution.""" name: str = field( - metadata={"description": "Name of the pipeline process to configure"}, + metadata={"description": "Name of the pipeline process as it appears in main.nf"}, ) parameters: Optional[list[str]] = field( default=None, @@ -216,7 +216,7 @@ class RunParams: ) version: Optional[int] = field( default=None, - metadata={"description": "Specific version of the capsule to run"}, + metadata={"description": "Specific version of the capsule or pipeline to run"}, ) resume_run_id: Optional[str] = field( default=None, @@ -234,7 +234,7 @@ class RunParams: ) parameters: Optional[list[str]] = field( default=None, - metadata={"description": "Ordered list of parameter values"}, + metadata={"description": "Ordered list of parameter values for the computation"}, ) named_parameters: Optional[list[NamedRunParam]] = field( default=None, @@ -268,9 +268,6 @@ def run_capsule(self, run_params: RunParams) -> Computation: For pipeline execution: Set run_params.pipeline_id and optionally provide data_assets, processes (with process-specific parameters), and nextflow_profile configuration. - Typical workflow: 1) run_capsule() to start execution, 2) wait_until_completed() to - monitor progress, 3) list_computation_results() and get_result_file_download_url() - to retrieve outputs. """ res = self.client.post("computations", json=run_params.to_dict()) diff --git a/src/codeocean/data_asset.py b/src/codeocean/data_asset.py index e423d52..4e4ecd8 100644 --- a/src/codeocean/data_asset.py +++ b/src/codeocean/data_asset.py @@ -36,7 +36,7 @@ class Provenance: commit: Optional[str] = field( default=None, - metadata={"description": "Commit hash the data asset was created from"}, + metadata={"description": "Commit hash of capsule/pipeline code at time of execution"}, ) run_script: Optional[str] = field( default=None, @@ -48,17 +48,17 @@ class Provenance: ) capsule: Optional[str] = field( default=None, - metadata={"description": "Capsule used to create the data asset"}, + metadata={"description": "ID of the capsule used to create the data asset"}, ) data_assets: Optional[list[str]] = field( default=None, metadata={ - "description": "Data assets that were used to create this data asset", + "description": "Data assets that were used as input to create this data asset", }, ) computation: Optional[str] = field( default=None, - metadata={"description": "Computation ID used to create the data asset"}, + metadata={"description": "ID of the computation from which this data asset was created"}, ) @@ -80,7 +80,7 @@ class SourceBucket: ) bucket: Optional[str] = field( default=None, - metadata={"description": "The original bucket's name"}, + metadata={"description": "The S3 bucket from which the data asset was created"}, ) prefix: Optional[str] = field( default=None, @@ -90,7 +90,7 @@ class SourceBucket: ) external: Optional[bool] = field( default=None, - metadata={"description": "Indicates if the data asset is stored externally"}, + metadata={"description": "Indicates if the data asset is stored external to Code Ocean"}, ) @@ -168,7 +168,7 @@ class DataAsset: metadata={"description": "Unique data asset ID (UUID string)"}, ) created: int = field( - metadata={"description": "Data asset creation time (seconds since epoch)"}, + metadata={"description": "Data asset creation time in seconds from epoch"}, ) name: str = field( metadata={"description": "Name of the data asset"}, @@ -507,7 +507,7 @@ class DataAssetAttachResults: ) mount: Optional[str] = field( default=None, - metadata={"description": "Actual mount path used for the data asset"}, + metadata={"description": "Path at which the data asset is mounted"}, ) @@ -716,12 +716,6 @@ def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset: request validity, not success, as creation takes time. Use wait_until_ready() to monitor creation progress. - Typical workflow: - 1) create_data_asset() to initiate creation - 2) wait_until_ready() to monitor progress until state is 'ready' - 3) get_data_asset() to fetch final metadata - 4) list_data_asset_files() and get_data_asset_file_download_url() - to access contents """ res = self.client.post("data_assets", json=data_asset_params.to_dict())