aind-codeocean-dispatch 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/CHANGELOG.md +6 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/PKG-INFO +2 -2
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/pyproject.toml +2 -2
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/src/aind_codeocean_dispatch/runner.py +12 -6
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/tests/fakes.py +18 -7
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/tests/test_runner.py +5 -2
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/uv.lock +2 -2
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.copier-answers.yml +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/ISSUE_TEMPLATE/user-story.md +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/dependabot.yml +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/workflows/bump-call.yml +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/workflows/ci-call.yml +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/workflows/publish-call.yml +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.gitignore +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.python-version +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/CLAUDE.md +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/LICENSE +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/README.md +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/scripts/run_linters_and_checks.sh +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/src/aind_codeocean_dispatch/__init__.py +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/src/aind_codeocean_dispatch/py.typed +0 -0
- {aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/tests/__init__.py +0 -0
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v0.2.1 (2026-10-15)
|
|
9
|
+
|
|
10
|
+
### Fix
|
|
11
|
+
|
|
12
|
+
- support codeocean 0.13 and reuse only the caller's own captures
|
|
13
|
+
|
|
8
14
|
## v0.2.0 (2026-10-15)
|
|
9
15
|
|
|
10
16
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: aind-codeocean-dispatch
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Launch, monitor, and capture batches of Code Ocean capsule runs
|
|
5
5
|
Project-URL: Repository, https://github.com/AllenNeuralDynamics/aind-codeocean-dispatch/
|
|
6
6
|
Project-URL: Issues, https://github.com/AllenNeuralDynamics/aind-codeocean-dispatch/issues
|
|
@@ -12,7 +12,7 @@ License-File: LICENSE
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Requires-Python: >=3.11
|
|
14
14
|
Requires-Dist: aind-code-ocean-pipeline-utils>=0.7.1
|
|
15
|
-
Requires-Dist: codeocean>=0.
|
|
15
|
+
Requires-Dist: codeocean>=0.13
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
# AIND Code Ocean dispatch
|
|
@@ -13,11 +13,11 @@ authors = [
|
|
|
13
13
|
]
|
|
14
14
|
classifiers = ["Programming Language :: Python :: 3"]
|
|
15
15
|
readme = "README.md"
|
|
16
|
-
version = "0.2.
|
|
16
|
+
version = "0.2.1"
|
|
17
17
|
|
|
18
18
|
dependencies = [
|
|
19
19
|
"aind-code-ocean-pipeline-utils>=0.7.1",
|
|
20
|
-
"codeocean>=0.
|
|
20
|
+
"codeocean>=0.13",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[project.urls]
|
|
@@ -33,9 +33,13 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
|
33
33
|
import requests
|
|
34
34
|
from aind_code_ocean_pipeline_utils.cache import input_fingerprint
|
|
35
35
|
from aind_code_ocean_pipeline_utils.process import GracefulExit, check_shutdown
|
|
36
|
+
|
|
37
|
+
# Import paths that exist across supported SDK versions; 0.17 moved the models to
|
|
38
|
+
# ``codeocean.models`` and kept these as re-exports.
|
|
39
|
+
from codeocean.components import Ownership
|
|
40
|
+
from codeocean.computation import Computation, ComputationEndStatus, ComputationState, RunParams
|
|
41
|
+
from codeocean.data_asset import DataAsset, DataAssetParams, DataAssetSearchParams, DataAssetState
|
|
36
42
|
from codeocean.error import Error as CodeOceanError
|
|
37
|
-
from codeocean.models.computation import Computation, ComputationEndStatus, ComputationState, RunParams
|
|
38
|
-
from codeocean.models.data_asset import DataAsset, DataAssetParams, DataAssetSearchParams, DataAssetState
|
|
39
43
|
|
|
40
44
|
if TYPE_CHECKING:
|
|
41
45
|
from codeocean import CodeOcean
|
|
@@ -88,8 +92,8 @@ class Job:
|
|
|
88
92
|
can depend on it. ``None`` stops at a successful computation.
|
|
89
93
|
key : str, optional
|
|
90
94
|
Resume key (see :func:`job_key`), added to the captured asset as
|
|
91
|
-
:func:`key_tag`. With ``reuse=True``, a Ready asset carrying it
|
|
92
|
-
instead of launching. ``None`` never reuses.
|
|
95
|
+
:func:`key_tag`. With ``reuse=True``, a Ready asset carrying it that the
|
|
96
|
+
caller created is returned instead of launching. ``None`` never reuses.
|
|
93
97
|
"""
|
|
94
98
|
|
|
95
99
|
label: str
|
|
@@ -243,7 +247,9 @@ def run_jobs(
|
|
|
243
247
|
capture_retries : int, optional
|
|
244
248
|
New capture attempts per job after a capture ends in the Failed state.
|
|
245
249
|
reuse : bool, optional
|
|
246
|
-
Return a Ready asset tagged with a job's key instead of launching it.
|
|
250
|
+
Return a Ready asset tagged with a job's key instead of launching it. Only
|
|
251
|
+
assets the caller created count: another user running identical inputs
|
|
252
|
+
produces the same key, and their asset is not the caller's to consume.
|
|
247
253
|
poll_interval_s : float, optional
|
|
248
254
|
Seconds between status checks of each running computation.
|
|
249
255
|
timeout_s : float, optional
|
|
@@ -479,7 +485,7 @@ class _Dispatcher:
|
|
|
479
485
|
if slot.next_at > _now():
|
|
480
486
|
continue
|
|
481
487
|
tag = key_tag(slot.job.key)
|
|
482
|
-
params = DataAssetSearchParams(query=f"tag:{tag}", limit=100)
|
|
488
|
+
params = DataAssetSearchParams(query=f"tag:{tag}", ownership=Ownership.Created, limit=100)
|
|
483
489
|
try:
|
|
484
490
|
results = self.client.data_assets.search_data_assets(params)
|
|
485
491
|
except (CodeOceanError, requests.RequestException) as exc:
|
|
@@ -8,14 +8,14 @@ creation order.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import dataclasses
|
|
11
12
|
import itertools
|
|
12
13
|
from dataclasses import dataclass, field
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
15
16
|
import requests
|
|
16
|
-
from codeocean.
|
|
17
|
-
from codeocean.
|
|
18
|
-
from codeocean.models.data_asset import (
|
|
17
|
+
from codeocean.computation import Computation, ComputationEndStatus, ComputationState, RunParams
|
|
18
|
+
from codeocean.data_asset import (
|
|
19
19
|
DataAsset,
|
|
20
20
|
DataAssetParams,
|
|
21
21
|
DataAssetSearchParams,
|
|
@@ -23,6 +23,16 @@ from codeocean.models.data_asset import (
|
|
|
23
23
|
DataAssetState,
|
|
24
24
|
DataAssetType,
|
|
25
25
|
)
|
|
26
|
+
from codeocean.error import Error as CodeOceanError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build(cls: Any, **values: Any) -> Any:
|
|
30
|
+
"""Construct an SDK model, dropping fields this SDK version does not declare.
|
|
31
|
+
|
|
32
|
+
Required fields differ across supported versions (0.17 added ``owner``).
|
|
33
|
+
"""
|
|
34
|
+
declared = {f.name for f in dataclasses.fields(cls)}
|
|
35
|
+
return cls(**{name: value for name, value in values.items() if name in declared})
|
|
26
36
|
|
|
27
37
|
|
|
28
38
|
class FakeClock:
|
|
@@ -69,7 +79,8 @@ CRASHED = snapshot(ComputationState.Completed, end_status=ComputationEndStatus.F
|
|
|
69
79
|
|
|
70
80
|
|
|
71
81
|
def asset(asset_id: str, state: DataAssetState, *, tags: list[str] | None = None, created: int = 0) -> DataAsset:
|
|
72
|
-
return
|
|
82
|
+
return build(
|
|
83
|
+
DataAsset,
|
|
73
84
|
id=asset_id,
|
|
74
85
|
created=created,
|
|
75
86
|
name=asset_id,
|
|
@@ -115,7 +126,7 @@ class FakeComputations:
|
|
|
115
126
|
|
|
116
127
|
@staticmethod
|
|
117
128
|
def _computation(computation_id: str, step: dict[str, Any]) -> Computation:
|
|
118
|
-
return Computation
|
|
129
|
+
return build(Computation, id=computation_id, created=0, name=computation_id, owner="owner", run_time=0, **step)
|
|
119
130
|
|
|
120
131
|
|
|
121
132
|
@dataclass
|
|
@@ -125,7 +136,7 @@ class FakeDataAssets:
|
|
|
125
136
|
existing: list[DataAsset] = field(default_factory=list)
|
|
126
137
|
search_errors: list[Exception] = field(default_factory=list)
|
|
127
138
|
created: list[DataAssetParams] = field(default_factory=list)
|
|
128
|
-
searches: list[
|
|
139
|
+
searches: list[DataAssetSearchParams] = field(default_factory=list)
|
|
129
140
|
_scripts: dict[str, list[Any]] = field(default_factory=dict)
|
|
130
141
|
|
|
131
142
|
def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset:
|
|
@@ -145,7 +156,7 @@ class FakeDataAssets:
|
|
|
145
156
|
return asset(data_asset_id, step)
|
|
146
157
|
|
|
147
158
|
def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults:
|
|
148
|
-
self.searches.append(search_params
|
|
159
|
+
self.searches.append(search_params)
|
|
149
160
|
if self.search_errors:
|
|
150
161
|
raise self.search_errors.pop(0)
|
|
151
162
|
return DataAssetSearchResults(has_more=False, results=list(self.existing))
|
|
@@ -5,8 +5,9 @@ from typing import Any
|
|
|
5
5
|
import pytest
|
|
6
6
|
import requests
|
|
7
7
|
from aind_code_ocean_pipeline_utils.process import GracefulExit
|
|
8
|
-
from codeocean.
|
|
9
|
-
from codeocean.
|
|
8
|
+
from codeocean.components import Ownership
|
|
9
|
+
from codeocean.computation import Computation, DataAssetsRunParam, NamedRunParam, RunParams
|
|
10
|
+
from codeocean.data_asset import DataAssetParams, DataAssetState
|
|
10
11
|
|
|
11
12
|
from aind_codeocean_dispatch import (
|
|
12
13
|
DispatchFailed,
|
|
@@ -255,6 +256,8 @@ def test_reuse_returns_newest_ready_asset_with_exact_tag(clock: FakeClock) -> No
|
|
|
255
256
|
assert outcome.status == "reused"
|
|
256
257
|
assert outcome.asset_id == "new"
|
|
257
258
|
assert client.computations.launches == []
|
|
259
|
+
(search,) = client.data_assets.searches
|
|
260
|
+
assert search.ownership == Ownership.Created
|
|
258
261
|
|
|
259
262
|
|
|
260
263
|
def test_reuse_launches_when_nothing_matches(clock: FakeClock) -> None:
|
|
@@ -17,7 +17,7 @@ wheels = [
|
|
|
17
17
|
|
|
18
18
|
[[package]]
|
|
19
19
|
name = "aind-codeocean-dispatch"
|
|
20
|
-
version = "0.2.
|
|
20
|
+
version = "0.2.1"
|
|
21
21
|
source = { editable = "." }
|
|
22
22
|
dependencies = [
|
|
23
23
|
{ name = "aind-code-ocean-pipeline-utils" },
|
|
@@ -50,7 +50,7 @@ types = [
|
|
|
50
50
|
[package.metadata]
|
|
51
51
|
requires-dist = [
|
|
52
52
|
{ name = "aind-code-ocean-pipeline-utils", specifier = ">=0.7.1" },
|
|
53
|
-
{ name = "codeocean", specifier = ">=0.
|
|
53
|
+
{ name = "codeocean", specifier = ">=0.13" },
|
|
54
54
|
]
|
|
55
55
|
|
|
56
56
|
[package.metadata.requires-dev]
|
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/ISSUE_TEMPLATE/user-story.md
RENAMED
|
File without changes
|
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/workflows/bump-call.yml
RENAMED
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/workflows/ci-call.yml
RENAMED
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/.github/workflows/publish-call.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/scripts/run_linters_and_checks.sh
RENAMED
|
File without changes
|
|
File without changes
|
{aind_codeocean_dispatch-0.2.0 → aind_codeocean_dispatch-0.2.1}/src/aind_codeocean_dispatch/py.typed
RENAMED
|
File without changes
|
|
File without changes
|