aind-codeocean-dispatch 0.2.1__tar.gz → 0.2.2__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.
Files changed (24) hide show
  1. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/CHANGELOG.md +6 -0
  2. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/PKG-INFO +1 -1
  3. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/pyproject.toml +1 -1
  4. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/src/aind_codeocean_dispatch/runner.py +18 -3
  5. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/tests/fakes.py +1 -0
  6. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/tests/test_runner.py +29 -0
  7. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/uv.lock +1 -1
  8. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.copier-answers.yml +0 -0
  9. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  10. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  11. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/ISSUE_TEMPLATE/user-story.md +0 -0
  12. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/dependabot.yml +0 -0
  13. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/workflows/bump-call.yml +0 -0
  14. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/workflows/ci-call.yml +0 -0
  15. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.github/workflows/publish-call.yml +0 -0
  16. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.gitignore +0 -0
  17. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/.python-version +0 -0
  18. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/CLAUDE.md +0 -0
  19. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/LICENSE +0 -0
  20. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/README.md +0 -0
  21. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/scripts/run_linters_and_checks.sh +0 -0
  22. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/src/aind_codeocean_dispatch/__init__.py +0 -0
  23. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/src/aind_codeocean_dispatch/py.typed +0 -0
  24. {aind_codeocean_dispatch-0.2.1 → aind_codeocean_dispatch-0.2.2}/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.2 (2026-10-15)
9
+
10
+ ### Fix
11
+
12
+ - run on a non-retrying copy of the caller's client
13
+
8
14
  ## v0.2.1 (2026-10-15)
9
15
 
10
16
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: aind-codeocean-dispatch
3
- Version: 0.2.1
3
+ Version: 0.2.2
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
@@ -13,7 +13,7 @@ authors = [
13
13
  ]
14
14
  classifiers = ["Programming Language :: Python :: 3"]
15
15
  readme = "README.md"
16
- version = "0.2.1"
16
+ version = "0.2.2"
17
17
 
18
18
  dependencies = [
19
19
  "aind-code-ocean-pipeline-utils>=0.7.1",
@@ -6,7 +6,9 @@ counters rather than locks, and no thread is parked per job.
6
6
 
7
7
  Error policy
8
8
  ------------
9
- The SDK's client retries nothing by default, so every 429 or 5xx surfaces here.
9
+ Every 429 or 5xx must surface here, so :func:`run_jobs` works on a copy of the
10
+ client with ``retries=0``: transport-level retries would resend POSTs below this
11
+ policy, and their exhausted retries raise without the status code it reads.
10
12
 
11
13
  - **429** is retried on every call, honouring ``Retry-After``: the server rejected
12
14
  the request without acting on it.
@@ -232,7 +234,8 @@ def run_jobs(
232
234
  Parameters
233
235
  ----------
234
236
  client : CodeOcean
235
- Authenticated client.
237
+ Authenticated client. Its own ``retries`` setting is overridden on a copy;
238
+ the caller's client is left unchanged.
236
239
  jobs : Sequence[Job]
237
240
  Launched in order, subject to the cap and pacing.
238
241
  max_in_flight : int, optional
@@ -271,7 +274,7 @@ def run_jobs(
271
274
  """
272
275
  _validate(jobs, max_in_flight=max_in_flight, start_retries=start_retries, capture_retries=capture_retries)
273
276
  return _Dispatcher(
274
- client,
277
+ _without_transport_retries(client),
275
278
  jobs,
276
279
  max_in_flight=max_in_flight,
277
280
  launch_interval_s=launch_interval_s,
@@ -284,6 +287,18 @@ def run_jobs(
284
287
  ).run()
285
288
 
286
289
 
290
+ def _without_transport_retries(client: CodeOcean) -> CodeOcean:
291
+ """Return *client*, or a copy with ``retries=0`` if it retries requests itself.
292
+
293
+ The copy opens a new session, so anything mounted on the original's ``session``
294
+ by hand is not carried over.
295
+ """
296
+ if client.retries in (0, None):
297
+ return client
298
+ logger.info("using a copy of the client with retries=0 (was %r); run_jobs retries what is safe", client.retries)
299
+ return dataclasses.replace(client, retries=0)
300
+
301
+
287
302
  def _validate(jobs: Sequence[Job], *, max_in_flight: int | None, start_retries: int, capture_retries: int) -> None:
288
303
  labels = [job.label for job in jobs]
289
304
  if len(set(labels)) != len(labels):
@@ -166,3 +166,4 @@ class FakeDataAssets:
166
166
  class FakeClient:
167
167
  computations: FakeComputations
168
168
  data_assets: FakeDataAssets
169
+ retries: Any = 0
@@ -5,9 +5,11 @@ 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 import CodeOcean
8
9
  from codeocean.components import Ownership
9
10
  from codeocean.computation import Computation, DataAssetsRunParam, NamedRunParam, RunParams
10
11
  from codeocean.data_asset import DataAssetParams, DataAssetState
12
+ from urllib3.util import Retry
11
13
 
12
14
  from aind_codeocean_dispatch import (
13
15
  DispatchFailed,
@@ -315,6 +317,33 @@ def test_duplicate_labels_are_rejected(clock: FakeClock) -> None:
315
317
  run_jobs(make_client(clock, {}), [job("a"), job("a")])
316
318
 
317
319
 
320
+ @pytest.mark.parametrize("retries", [3, Retry(total=5, allowed_methods=["POST"])])
321
+ def test_client_that_retries_itself_still_runs(clock: FakeClock, retries: Any) -> None:
322
+ client = make_client(clock, {"a": [[SUCCEEDED]]})
323
+ client.retries = retries
324
+
325
+ outcomes = run_jobs(client, [job("a")])
326
+
327
+ assert [o.status for o in outcomes] == ["captured"]
328
+ assert client.retries is retries
329
+
330
+
331
+ def test_retrying_client_is_copied_without_retries() -> None:
332
+ original = CodeOcean(domain="https://codeocean.example", token="x", retries=Retry(total=5))
333
+
334
+ copy = runner._without_transport_retries(original)
335
+
336
+ assert copy is not original and copy.session is not original.session
337
+ assert (copy.retries, copy.domain, copy.token) == (0, original.domain, original.token)
338
+ assert isinstance(original.retries, Retry)
339
+
340
+
341
+ def test_non_retrying_client_is_used_as_is() -> None:
342
+ client = CodeOcean(domain="https://codeocean.example", token="x")
343
+
344
+ assert runner._without_transport_retries(client) is client
345
+
346
+
318
347
  def test_invalid_key_is_rejected() -> None:
319
348
  with pytest.raises(ValueError, match="key"):
320
349
  job("a", key="has space")
@@ -17,7 +17,7 @@ wheels = [
17
17
 
18
18
  [[package]]
19
19
  name = "aind-codeocean-dispatch"
20
- version = "0.2.1"
20
+ version = "0.2.2"
21
21
  source = { editable = "." }
22
22
  dependencies = [
23
23
  { name = "aind-code-ocean-pipeline-utils" },