arraybridge 0.2.11__tar.gz → 0.3.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.
Files changed (111) hide show
  1. {arraybridge-0.2.11 → arraybridge-0.3.1}/.github/workflows/ci.yml +1 -1
  2. {arraybridge-0.2.11 → arraybridge-0.3.1}/.github/workflows/coverage-pages.yml +4 -15
  3. {arraybridge-0.2.11 → arraybridge-0.3.1}/.github/workflows/publish.yml +20 -4
  4. arraybridge-0.3.1/ADDING_NEW_FRAMEWORKS.md +63 -0
  5. {arraybridge-0.2.11 → arraybridge-0.3.1}/CONTRIBUTING.md +36 -21
  6. {arraybridge-0.2.11 → arraybridge-0.3.1}/PKG-INFO +14 -8
  7. {arraybridge-0.2.11 → arraybridge-0.3.1}/README.md +10 -7
  8. arraybridge-0.3.1/TESTING.md +46 -0
  9. arraybridge-0.3.1/docs/source/advanced_topics.rst +24 -0
  10. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/api_reference.rst +8 -1
  11. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/conf.py +1 -1
  12. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/contributing.rst +3 -3
  13. arraybridge-0.3.1/docs/source/converters.rst +47 -0
  14. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/decorators.rst +19 -0
  15. arraybridge-0.3.1/docs/source/gpu_features.rst +34 -0
  16. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/user_guide.rst +4 -4
  17. {arraybridge-0.2.11 → arraybridge-0.3.1}/pyproject.toml +12 -2
  18. arraybridge-0.3.1/scripts/release.py +117 -0
  19. arraybridge-0.3.1/scripts/update_version.py +79 -0
  20. arraybridge-0.3.1/scripts/verify_release_ready.py +307 -0
  21. {arraybridge-0.2.11 → arraybridge-0.3.1}/src/arraybridge/__init__.py +24 -19
  22. arraybridge-0.3.1/src/arraybridge/array_geometry.py +57 -0
  23. arraybridge-0.3.1/src/arraybridge/array_operations.py +396 -0
  24. arraybridge-0.3.1/src/arraybridge/array_payload.py +31 -0
  25. arraybridge-0.3.1/src/arraybridge/converters.py +85 -0
  26. {arraybridge-0.2.11 → arraybridge-0.3.1}/src/arraybridge/converters_registry.py +47 -69
  27. {arraybridge-0.2.11 → arraybridge-0.3.1}/src/arraybridge/decorators.py +140 -205
  28. arraybridge-0.3.1/src/arraybridge/dtype_scaling.py +25 -0
  29. arraybridge-0.3.1/src/arraybridge/framework_config.py +26 -0
  30. arraybridge-0.3.1/src/arraybridge/framework_ops.py +7 -0
  31. arraybridge-0.3.1/src/arraybridge/gpu_cleanup.py +78 -0
  32. arraybridge-0.3.1/src/arraybridge/oom_recovery.py +90 -0
  33. {arraybridge-0.2.11 → arraybridge-0.3.1}/src/arraybridge/slice_processing.py +36 -5
  34. {arraybridge-0.2.11 → arraybridge-0.3.1}/src/arraybridge/stack_utils.py +23 -127
  35. arraybridge-0.3.1/src/arraybridge/types.py +984 -0
  36. arraybridge-0.3.1/src/arraybridge/utils.py +173 -0
  37. arraybridge-0.3.1/tests/test_array_geometry.py +53 -0
  38. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_converters.py +58 -0
  39. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_converters_registry.py +28 -24
  40. arraybridge-0.3.1/tests/test_decorators.py +541 -0
  41. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_dtype_scaling.py +85 -59
  42. arraybridge-0.3.1/tests/test_framework_config.py +29 -0
  43. arraybridge-0.3.1/tests/test_gpu_cleanup.py +156 -0
  44. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_registry_integration.py +2 -20
  45. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_slice_processing.py +29 -5
  46. arraybridge-0.3.1/tests/test_stack_utils.py +378 -0
  47. arraybridge-0.3.1/tests/test_types.py +446 -0
  48. arraybridge-0.3.1/tests/test_utils.py +248 -0
  49. arraybridge-0.2.11/.github/badges/coverage.svg +0 -1
  50. arraybridge-0.2.11/ADDING_NEW_FRAMEWORKS.md +0 -219
  51. arraybridge-0.2.11/SETUP_INSTRUCTIONS.md +0 -64
  52. arraybridge-0.2.11/TESTING.md +0 -175
  53. arraybridge-0.2.11/docs/source/advanced_topics.rst +0 -20
  54. arraybridge-0.2.11/docs/source/converters.rst +0 -36
  55. arraybridge-0.2.11/docs/source/gpu_features.rst +0 -19
  56. arraybridge-0.2.11/scripts/release.py +0 -63
  57. arraybridge-0.2.11/scripts/update_and_release.py +0 -114
  58. arraybridge-0.2.11/scripts/update_version.py +0 -111
  59. arraybridge-0.2.11/scripts/verify_release_ready.py +0 -274
  60. arraybridge-0.2.11/src/arraybridge/converters.py +0 -71
  61. arraybridge-0.2.11/src/arraybridge/dtype_scaling.py +0 -179
  62. arraybridge-0.2.11/src/arraybridge/framework_config.py +0 -442
  63. arraybridge-0.2.11/src/arraybridge/framework_ops.py +0 -14
  64. arraybridge-0.2.11/src/arraybridge/gpu_cleanup.py +0 -141
  65. arraybridge-0.2.11/src/arraybridge/oom_recovery.py +0 -148
  66. arraybridge-0.2.11/src/arraybridge/types.py +0 -47
  67. arraybridge-0.2.11/src/arraybridge/utils.py +0 -366
  68. arraybridge-0.2.11/tests/test_decorators.py +0 -250
  69. arraybridge-0.2.11/tests/test_framework_config.py +0 -412
  70. arraybridge-0.2.11/tests/test_gpu_cleanup.py +0 -244
  71. arraybridge-0.2.11/tests/test_stack_utils.py +0 -169
  72. arraybridge-0.2.11/tests/test_types.py +0 -71
  73. arraybridge-0.2.11/tests/test_utils.py +0 -404
  74. {arraybridge-0.2.11 → arraybridge-0.3.1}/.github/workflows/gpu-tests.yml +0 -0
  75. {arraybridge-0.2.11 → arraybridge-0.3.1}/.gitignore +0 -0
  76. {arraybridge-0.2.11 → arraybridge-0.3.1}/.readthedocs.yml +0 -0
  77. {arraybridge-0.2.11 → arraybridge-0.3.1}/LICENSE +0 -0
  78. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/Makefile +0 -0
  79. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/CI_ARTIFACT_UPDATE.md +0 -0
  80. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/CI_CUPY_FIX.md +0 -0
  81. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/CI_IMPROVEMENTS.md +0 -0
  82. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/CONFLICT_VISUALIZATION.md +0 -0
  83. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/COVERAGE_AUDIT_PLAN.md +0 -0
  84. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/DEPLOYMENT_SUMMARY.md +0 -0
  85. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/GPU_TESTING_SETUP.md +0 -0
  86. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/MERGE_CONFLICT_RESOLUTION.md +0 -0
  87. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/MIGRATION_SUMMARY.md +0 -0
  88. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/QUICK_FIX_GUIDE.txt +0 -0
  89. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/README.md +0 -0
  90. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/archive/RESOLVED_FILES.md +0 -0
  91. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/ci-cd.md +0 -0
  92. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/.gitignore +0 -0
  93. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/Makefile +0 -0
  94. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/README.md +0 -0
  95. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/ci-cd.rst +0 -0
  96. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/examples/basic_conversion.rst +0 -0
  97. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/examples/decorators.rst +0 -0
  98. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/examples/index.rst +0 -0
  99. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/examples/multi_framework.rst +0 -0
  100. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/index.rst +0 -0
  101. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/installation.rst +0 -0
  102. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/quickstart.rst +0 -0
  103. {arraybridge-0.2.11 → arraybridge-0.3.1}/docs/source/stack_utils.rst +0 -0
  104. {arraybridge-0.2.11 → arraybridge-0.3.1}/paper.bib +0 -0
  105. {arraybridge-0.2.11 → arraybridge-0.3.1}/paper.md +0 -0
  106. {arraybridge-0.2.11 → arraybridge-0.3.1}/src/arraybridge/exceptions.py +0 -0
  107. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/__init__.py +0 -0
  108. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/conftest.py +0 -0
  109. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_exceptions.py +0 -0
  110. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_integration.py +0 -0
  111. {arraybridge-0.2.11 → arraybridge-0.3.1}/tests/test_oom_recovery.py +0 -0
@@ -155,4 +155,4 @@ jobs:
155
155
  - name: Run mypy (type checking)
156
156
  continue-on-error: true # Scientific code often has complex types
157
157
  run: |
158
- mypy src/ --ignore-missing-imports
158
+ mypy src/
@@ -6,7 +6,7 @@ on:
6
6
  workflow_dispatch:
7
7
 
8
8
  permissions:
9
- contents: write # Needed for badge commit
9
+ contents: read
10
10
  pages: write
11
11
  id-token: write
12
12
 
@@ -40,7 +40,6 @@ jobs:
40
40
  pip install torch --index-url https://download.pytorch.org/whl/cpu
41
41
 
42
42
  - name: Run tests with coverage
43
- continue-on-error: true # Continue workflow even if tests fail
44
43
  run: |
45
44
  mkdir -p site
46
45
  python -m pytest --cov=arraybridge \
@@ -50,8 +49,7 @@ jobs:
50
49
 
51
50
  - name: Generate coverage badge
52
51
  run: |
53
- mkdir -p .github/badges
54
- genbadge coverage -i coverage.xml -o .github/badges/coverage.svg -n "coverage"
52
+ genbadge coverage -i coverage.xml -o site/coverage-badge.svg -n "coverage"
55
53
 
56
54
  - name: Create index.html and README
57
55
  run: |
@@ -72,7 +70,7 @@ jobs:
72
70
  # Create README.md in the site directory
73
71
  echo "# arraybridge Code Coverage Reports" > site/README.md
74
72
  echo "" >> site/README.md
75
- echo "This site contains the code coverage reports for the [arraybridge](https://github.com/trissim/arraybridge) project." >> site/README.md
73
+ echo "This site contains the code coverage reports for the [arraybridge](https://github.com/OpenHCSDev/arraybridge) project." >> site/README.md
76
74
  echo "" >> site/README.md
77
75
  echo "## Navigation" >> site/README.md
78
76
  echo "" >> site/README.md
@@ -85,19 +83,10 @@ jobs:
85
83
  echo "" >> site/README.md
86
84
  echo "Last updated: $(date)" >> site/README.md
87
85
 
88
- - name: Commit and push if coverage badge changed
89
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
90
- run: |
91
- git config --local user.email "github-actions[bot]@users.noreply.github.com"
92
- git config --local user.name "github-actions[bot]"
93
- git add .github/badges/coverage.svg -f
94
- git commit -m "chore: update coverage badge" || exit 0
95
- git push
96
-
97
86
  - name: Check GitHub Pages status
98
87
  run: |
99
88
  echo "⚠️ IMPORTANT: Make sure GitHub Pages is enabled in your repository settings!"
100
- echo "Go to https://github.com/trissim/arraybridge/settings/pages"
89
+ echo "Go to https://github.com/OpenHCSDev/arraybridge/settings/pages"
101
90
  echo "Set 'Source' to 'GitHub Actions' to enable GitHub Pages deployment."
102
91
 
103
92
  - name: Setup Pages
@@ -20,6 +20,8 @@ jobs:
20
20
  steps:
21
21
  - name: Checkout code
22
22
  uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
23
25
 
24
26
  - name: Set up Python
25
27
  uses: actions/setup-python@v5
@@ -29,7 +31,21 @@ jobs:
29
31
  - name: Install build dependencies
30
32
  run: |
31
33
  python -m pip install --upgrade pip
32
- pip install build
34
+ pip install -e ".[dev]"
35
+
36
+ - name: Validate source and tests
37
+ run: |
38
+ ruff check src/ scripts/
39
+ black --check src/ scripts/
40
+ mypy src/
41
+ pytest -q --no-cov
42
+
43
+ - name: Verify release tag and main ancestry
44
+ run: |
45
+ declared_version=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
46
+ test "$GITHUB_REF_NAME" = "v$declared_version"
47
+ git fetch origin main
48
+ git merge-base --is-ancestor "$GITHUB_SHA" origin/main
33
49
 
34
50
  - name: Build package
35
51
  run: python -m build
@@ -39,6 +55,9 @@ jobs:
39
55
  pip install twine
40
56
  twine check dist/*
41
57
 
58
+ - name: Publish to PyPI
59
+ uses: pypa/gh-action-pypi-publish@release/v1
60
+
42
61
  - name: Create GitHub Release
43
62
  uses: softprops/action-gh-release@v2
44
63
  with:
@@ -46,6 +65,3 @@ jobs:
46
65
  generate_release_notes: true
47
66
  env:
48
67
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49
-
50
- - name: Publish to PyPI
51
- uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,63 @@
1
+ # Adding a framework
2
+
3
+ This is a maintainer how-to for extending ArrayBridge's closed framework
4
+ taxonomy. Applications should consume `MemoryType`; they should not add their
5
+ own framework registry.
6
+
7
+ ## 1. Define typed leaves
8
+
9
+ Add an `ArrayOperations` bundle in
10
+ `src/arraybridge/array_operations.py`. It must provide four ordinary callables:
11
+
12
+ - project an array to NumPy;
13
+ - create an array from NumPy on the requested framework-local device;
14
+ - stack prepared 2D arrays into one 3D array;
15
+ - apply the framework's dtype-scaling semantics.
16
+
17
+ Define device, stream, DLPack, cleanup, and OOM leaves in
18
+ `src/arraybridge/types.py`. Use the no-op defaults in `FrameworkRuntime` only
19
+ when the framework genuinely lacks that capability. Do not encode behavior as
20
+ strings or add a parallel `MemoryType`-keyed table.
21
+
22
+ ## 2. Add the declaration
23
+
24
+ Add one `MemoryType` member carrying:
25
+
26
+ 1. its stable value and import name;
27
+ 2. its display name and module aliases;
28
+ 3. whether it is GPU-backed;
29
+ 4. any environment defaults that must be set before first import;
30
+ 5. its `FrameworkRuntime` leaves;
31
+ 6. its `ArrayOperations` bundle.
32
+
33
+ The converter classes, target conversion methods, compatibility operation view,
34
+ dtype-scaling view, cleanup adapters, and framework decorator are generated
35
+ from the enum. Do not wire them manually.
36
+
37
+ ## 3. Prove the extension
38
+
39
+ Add focused tests for:
40
+
41
+ - module-name detection and optional-dependency failure;
42
+ - NumPy round trips and every supported DLPack pair;
43
+ - CPU-only installations of a nominally GPU framework;
44
+ - invalid, negative, and multiple framework-local device IDs;
45
+ - same-framework moves between two devices;
46
+ - one-, two-, and multi-slice stack/unstack behavior;
47
+ - immutable array types;
48
+ - cleanup and OOM behavior without importing an absent framework;
49
+ - generated registry and export completeness.
50
+
51
+ Run:
52
+
53
+ ```bash
54
+ ruff check src tests
55
+ black --check src tests
56
+ mypy src --ignore-missing-imports
57
+ pytest
58
+ python -m sphinx -E -W --keep-going -b html docs/source /tmp/arraybridge-docs
59
+ ```
60
+
61
+ When GPU hardware is available, also exercise every installed framework pair in
62
+ one process. This catches import-order, allocator-coexistence, DLPack, and real
63
+ API-shape defects that isolated mocks cannot prove.
@@ -332,35 +332,48 @@ git push origin feature/your-feature-name
332
332
 
333
333
  ### 1. Update Version
334
334
 
335
- Edit `pyproject.toml`:
335
+ Update all three validated version projections with the side-effect-free helper:
336
336
 
337
- ```toml
338
- [project]
339
- version = "0.2.0"
337
+ ```bash
338
+ python scripts/update_version.py 0.3.0
340
339
  ```
341
340
 
341
+ Review the package, project, and documentation changes, then run
342
+ `python scripts/verify_release_ready.py --allow-dirty` before committing.
343
+
342
344
  ### 2. Update Changelog
343
345
 
344
346
  Add release notes to `CHANGELOG.md` (if exists) or create GitHub release notes.
345
347
 
346
- ### 3. Create Tag
348
+ ### 3. Validate, Commit, and Push
347
349
 
348
350
  ```bash
349
- # Ensure you're on main branch
350
- git checkout main
351
- git pull upstream main
351
+ ruff check src tests scripts
352
+ black --check src scripts
353
+ mypy src --ignore-missing-imports
354
+ pytest
355
+ python -m sphinx -E -W --keep-going -b html docs/source /tmp/arraybridge-docs
356
+ git commit
357
+ git push origin HEAD:main
358
+ ```
352
359
 
353
- # Create and push tag
354
- git tag -a v0.2.0 -m "Release version 0.2.0"
355
- git push upstream v0.2.0
360
+ ### 4. Create the Release Tag
361
+
362
+ After the pushed commit is exactly `origin/main`, run:
363
+
364
+ ```bash
365
+ python scripts/release.py
356
366
  ```
357
367
 
358
- ### 4. Automated Publishing
368
+ The script validates the clean published commit, confirms the PyPI version, and
369
+ pushes the matching annotated tag.
370
+
371
+ ### 5. Automated Publishing
359
372
 
360
373
  GitHub Actions will automatically:
361
- 1. Build the package
362
- 2. Create GitHub release
363
- 3. Publish to PyPI
374
+ 1. verify the tag, source, tests, and package metadata;
375
+ 2. publish to PyPI;
376
+ 3. create the GitHub release.
364
377
 
365
378
  ## Development Guidelines
366
379
 
@@ -391,12 +404,14 @@ GitHub Actions will automatically:
391
404
 
392
405
  To add support for a new framework:
393
406
 
394
- 1. Update `MemoryType` enum in `types.py`
395
- 2. Add framework config in `framework_config.py`
396
- 3. Implement converter in `conversion_helpers.py`
397
- 4. Add decorator support in `decorators.py`
398
- 5. Update documentation
399
- 6. Add tests with availability checks
407
+ 1. Add typed array-operation leaves in `array_operations.py`.
408
+ 2. Add one `MemoryType` declaration carrying those operations and its runtime leaves.
409
+ 3. Add detection, conversion-pair, stack, cross-device, and optional-dependency tests.
410
+ 4. Update the extension and reference documentation.
411
+
412
+ Do not add framework behavior to `_FRAMEWORK_CONFIG`, a consumer registry, or
413
+ generated decorator exports. See `ADDING_NEW_FRAMEWORKS.md` for the complete
414
+ declaration-owned extension procedure.
400
415
 
401
416
  ## Getting Help
402
417
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arraybridge
3
- Version: 0.2.11
3
+ Version: 0.3.1
4
4
  Summary: Unified API for NumPy, CuPy, PyTorch, TensorFlow, JAX, and pyclesperanto with automatic memory type conversion
5
5
  Project-URL: Homepage, https://github.com/OpenHCSDev/arraybridge
6
6
  Project-URL: Documentation, https://arraybridge.readthedocs.io
@@ -34,10 +34,13 @@ Provides-Extra: cupy
34
34
  Requires-Dist: cupy>=10.0; extra == 'cupy'
35
35
  Provides-Extra: dev
36
36
  Requires-Dist: black>=23.0; extra == 'dev'
37
+ Requires-Dist: build>=1.0; extra == 'dev'
37
38
  Requires-Dist: mypy>=1.0; extra == 'dev'
39
+ Requires-Dist: packaging>=23.0; extra == 'dev'
38
40
  Requires-Dist: pytest-cov>=4.0; extra == 'dev'
39
41
  Requires-Dist: pytest>=7.0; extra == 'dev'
40
42
  Requires-Dist: ruff>=0.1.0; extra == 'dev'
43
+ Requires-Dist: twine>=5.0; extra == 'dev'
41
44
  Provides-Extra: docs
42
45
  Requires-Dist: sphinx-autodoc-typehints>=1.25; extra == 'docs'
43
46
  Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
@@ -92,8 +95,10 @@ copy = convert_memory(
92
95
  ```
93
96
 
94
97
  `convert_memory` requires the declared source type, target type, and device id.
95
- It uses the registered converter for the source framework. The device id is
96
- required even for CPU conversions so call sites have one stable signature.
98
+ The source and target `MemoryType` declarations perform conversion directly.
99
+ The device id is required even for CPU conversions so call sites have one
100
+ stable signature. A GPU target must declare that identifier as available;
101
+ ArrayBridge does not silently place the value on the CPU.
97
102
 
98
103
  ## Declarative decorators
99
104
 
@@ -105,11 +110,12 @@ def normalize(image):
105
110
  return image / max(float(image.max()), 1.0)
106
111
  ```
107
112
 
108
- The framework decorators attach `input_memory_type` and `output_memory_type`
109
- metadata, provide dtype/slice runtime parameters, and add framework-specific
110
- stream/OOM handling where supported. They do **not** convert inputs or outputs
111
- between frameworks and do not accept a `gpu_id` argument. A host runtime must
112
- call `convert_memory` at the boundary it plans.
113
+ The framework decorators attach `input_memory_type`, `output_memory_type`, and
114
+ `execution_memory_type` metadata, provide dtype/slice runtime parameters, and
115
+ add framework-specific stream/OOM handling where supported. They do **not**
116
+ convert inputs or outputs between frameworks and do not accept a `gpu_id`
117
+ argument. A host runtime must call `convert_memory` at the boundary it plans and
118
+ scope execution using the execution declaration.
113
119
 
114
120
  ## Stack utilities
115
121
 
@@ -25,8 +25,10 @@ copy = convert_memory(
25
25
  ```
26
26
 
27
27
  `convert_memory` requires the declared source type, target type, and device id.
28
- It uses the registered converter for the source framework. The device id is
29
- required even for CPU conversions so call sites have one stable signature.
28
+ The source and target `MemoryType` declarations perform conversion directly.
29
+ The device id is required even for CPU conversions so call sites have one
30
+ stable signature. A GPU target must declare that identifier as available;
31
+ ArrayBridge does not silently place the value on the CPU.
30
32
 
31
33
  ## Declarative decorators
32
34
 
@@ -38,11 +40,12 @@ def normalize(image):
38
40
  return image / max(float(image.max()), 1.0)
39
41
  ```
40
42
 
41
- The framework decorators attach `input_memory_type` and `output_memory_type`
42
- metadata, provide dtype/slice runtime parameters, and add framework-specific
43
- stream/OOM handling where supported. They do **not** convert inputs or outputs
44
- between frameworks and do not accept a `gpu_id` argument. A host runtime must
45
- call `convert_memory` at the boundary it plans.
43
+ The framework decorators attach `input_memory_type`, `output_memory_type`, and
44
+ `execution_memory_type` metadata, provide dtype/slice runtime parameters, and
45
+ add framework-specific stream/OOM handling where supported. They do **not**
46
+ convert inputs or outputs between frameworks and do not accept a `gpu_id`
47
+ argument. A host runtime must call `convert_memory` at the boundary it plans and
48
+ scope execution using the execution declaration.
46
49
 
47
50
  ## Stack utilities
48
51
 
@@ -0,0 +1,46 @@
1
+ # Testing ArrayBridge
2
+
3
+ This is the practical test guide. The workflow files under `.github/workflows`
4
+ are the authority for the current CI matrix.
5
+
6
+ ## Local core checks
7
+
8
+ ```bash
9
+ python -m pip install -e ".[dev,docs]"
10
+ ruff check src tests
11
+ black --check src tests
12
+ mypy src --ignore-missing-imports
13
+ pytest
14
+ python -m sphinx -E -W --keep-going -b html docs/source /tmp/arraybridge-docs
15
+ ```
16
+
17
+ The default suite uses real optional frameworks when installed and otherwise
18
+ exercises declared unavailable paths and focused fakes. Markers such as
19
+ `torch`, `tensorflow`, `jax`, `cupy`, `pyclesperanto`, and `gpu` can select or
20
+ exclude framework-specific tests.
21
+
22
+ ## Real GPU lattice check
23
+
24
+ On a GPU host, validate more than a single NumPy round trip:
25
+
26
+ - import all installed frameworks in one fresh process;
27
+ - enumerate framework-local devices through each `MemoryType`;
28
+ - convert a small exact array through every available source-target pair;
29
+ - stack and unstack one, two, and several planes in every framework;
30
+ - verify same-framework movement across multiple local devices when present;
31
+ - confirm absent frameworks are not imported by discovery or cleanup;
32
+ - exercise DLPack success and explicit CPU fallback paths;
33
+ - target OOM cleanup to the device that owned execution.
34
+
35
+ Use a fresh process and unset TensorFlow/JAX allocator variables when checking
36
+ that declaration-owned import defaults work. Framework warnings about shared
37
+ CUDA plugin registration should be recorded separately from value, device, or
38
+ shape failures.
39
+
40
+ ## Interpreting CI
41
+
42
+ The main CI workflow runs the test suite across its declared Python and OS
43
+ matrix. The manual GPU-named workflow installs CPU-capable Torch and JAX on a
44
+ standard runner; it does not prove CUDA behavior. Real multi-framework and
45
+ multi-device results therefore remain a required release check on suitable
46
+ hardware.
@@ -0,0 +1,24 @@
1
+ Advanced extension topics
2
+ =========================
3
+
4
+ Framework behavior is carried by each ``MemoryType`` member. Add a framework by
5
+ extending that owning declaration:
6
+
7
+ 1. add the ``MemoryType`` member;
8
+ 2. provide its typed runtime and array-operation leaves;
9
+ 3. define conversion, stack, scaling, and device behavior;
10
+ 4. add cleanup and OOM behavior only when the framework supports them;
11
+ 5. exercise detection and every supported conversion pair.
12
+
13
+ Do not add a parallel framework-name table in a consumer. Generic consumers use
14
+ ``MemoryType`` directly. The converter registry and historical framework config
15
+ are generated projections, not authorities.
16
+
17
+ Version 0.3 keeps the private ``_FRAMEWORK_CONFIG`` and ``_FRAMEWORK_OPS`` names
18
+ only as read-only operation views. Their historical mapping shape is not a
19
+ compatibility contract. Migrate callers to ``MemoryType`` methods.
20
+
21
+ Conversion caching and batching belong to the caller because value lifetime and
22
+ semantic identity are application concerns. ArrayBridge functions remain
23
+ stateless at the conversion boundary except for declared thread-local GPU
24
+ context.
@@ -5,11 +5,18 @@ Public imports
5
5
  --------------
6
6
 
7
7
  ``MemoryType``, ``CPU_MEMORY_TYPES``, ``GPU_MEMORY_TYPES``, ``SUPPORTED_MEMORY_TYPES``
8
- Nominal framework identities.
8
+ Framework declarations. Each member owns import identity, conversion,
9
+ stacking, dtype scaling, device discovery, scoped selection, movement,
10
+ DLPack import/export, cleanup, and pre-import coexistence defaults.
9
11
 
10
12
  ``detect_memory_type`` and ``convert_memory``
11
13
  Detection and explicit conversion.
12
14
 
15
+ ``ArrayGeometry``
16
+ Framework-neutral shape inspection for arrays and nominal ``ArrayPayload``
17
+ values. Geometry inspection reads declared shape metadata and does not move
18
+ device data to host memory.
19
+
13
20
  ``memory_types``, ``numpy``, ``cupy``, ``torch``, ``tensorflow``, ``jax``, ``pyclesperanto``
14
21
  Callable memory declarations and wrappers.
15
22
 
@@ -22,7 +22,7 @@ sys.path.insert(0, str(PACKAGE_ROOT / "src"))
22
22
  project = 'arraybridge'
23
23
  copyright = '2025, Tristan Simas'
24
24
  author = 'Tristan Simas'
25
- release = '0.2.11'
25
+ release = '0.3.1'
26
26
 
27
27
  # -- General configuration ---------------------------------------------------
28
28
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -17,9 +17,9 @@ Framework extensions
17
17
  --------------------
18
18
 
19
19
  A framework extension must update the nominal ``MemoryType`` declaration and
20
- the owning conversion/framework strategies. Add detection tests, conversion
21
- pair tests, stack tests, and optional-dependency behavior. Consumers should not
22
- maintain a second framework-name registry.
20
+ its typed operation leaves. Add detection, conversion-pair, stack,
21
+ cross-device, and optional-dependency tests. Consumers should not maintain a
22
+ second framework-name registry.
23
23
 
24
24
  See :doc:`advanced_topics` for the ownership model and the repository's
25
25
  `contribution guide
@@ -0,0 +1,47 @@
1
+ Conversion system
2
+ =================
3
+
4
+ ``convert_memory(data, source_type, target_type, gpu_id)`` resolves the source
5
+ and target ``MemoryType`` declarations and lets those declarations perform the
6
+ conversion.
7
+
8
+ .. code-block:: python
9
+
10
+ import numpy as np
11
+
12
+ from arraybridge import MemoryType, convert_memory
13
+
14
+ value = np.arange(4)
15
+ result = convert_memory(
16
+ value,
17
+ MemoryType.NUMPY,
18
+ MemoryType.NUMPY,
19
+ 0,
20
+ )
21
+
22
+ Both ``source_type`` and ``target_type`` accept either the canonical
23
+ ``MemoryType`` member or its string value. Decorators normalize the same
24
+ members to string metadata at declaration time, so callable contracts and
25
+ conversion plans share one taxonomy without carrying duplicate enum classes.
26
+
27
+ Call ``detect_memory_type`` when the source type is not already known. Passing a
28
+ wrong source declaration is a caller error; conversion planning should keep the
29
+ declared type aligned with the actual value.
30
+
31
+ Device semantics
32
+ ----------------
33
+
34
+ ``gpu_id`` is required. GPU converters use it for allocation or device
35
+ selection. CPU conversion accepts the same argument for a uniform API. Moving a
36
+ value between two devices is expressed as conversion with the target device id.
37
+
38
+ Declarations prefer framework-native or DLPack paths when both endpoints
39
+ support them and otherwise use an explicit NumPy round trip. No zero-copy
40
+ guarantee applies to every pair.
41
+
42
+ Failures
43
+ --------
44
+
45
+ Invalid framework names raise ``ValueError``. Conversion failures raise
46
+ ``MemoryConversionError`` with source/target context. Optional frameworks are
47
+ loaded only when their declaration is used.
@@ -27,6 +27,21 @@ metadata, add keyword-only ``slice_by_slice`` and ``dtype_config`` runtime
27
27
  parameters, apply the selected dtype policy, and wrap supported GPU frameworks
28
28
  with thread-local stream/OOM handling.
29
29
 
30
+ Framework helpers also attach ``execution_memory_type``. It identifies the
31
+ framework that owns the function body even when ``input_type`` or
32
+ ``output_type`` is overridden. Host runtimes use that declaration when scoping
33
+ callable execution; boundary types alone do not prove execution ownership.
34
+
35
+ Metadata key API
36
+ ----------------
37
+
38
+ ``MemoryContractAttribute`` is the public owner of the callable metadata keys:
39
+ ``INPUT`` maps to ``input_memory_type``, ``OUTPUT`` maps to
40
+ ``output_memory_type``, and ``EXECUTION`` maps to ``execution_memory_type``.
41
+ Each member provides ``read(namespace, default=None)`` and
42
+ ``write(namespace, value)`` for object and mapping namespaces. Host libraries
43
+ can consume these members without maintaining a duplicate key registry.
44
+
30
45
  The decorator does not accept ``gpu_id`` or ``clear_cuda_cache``. Device
31
46
  selection belongs to explicit conversion or the host runtime. A non-callable
32
47
  ``contract`` is stored as declarative metadata; a callable contract validates
@@ -38,3 +53,7 @@ Dtype policy
38
53
  Direct calls default to preserving the input dtype. Hosts can pass an object
39
54
  implementing ``DtypeConversionConfig`` to select native or explicit output dtype
40
55
  behavior.
56
+
57
+ ``wrap_dtype_preserving_callable(func, memory_type)`` exposes the dtype and
58
+ slice-control wrapper as a public integration boundary for host registries that
59
+ already own their memory and processing contracts.
@@ -0,0 +1,34 @@
1
+ GPU lifecycle
2
+ =============
3
+
4
+ GPU-backed conversion uses the explicit ``gpu_id`` passed to
5
+ ``convert_memory``, ``stack_slices``, or ``unstack_slices``. Framework-specific
6
+ ``MemoryType`` declarations own device discovery, validation, scopes, movement,
7
+ and cache cleanup. Device identifiers are local to one framework. Equal numeric
8
+ identifiers from different frameworks do not establish physical-device
9
+ identity.
10
+
11
+ Before importing TensorFlow or JAX, their declarations apply coexistence-safe
12
+ defaults with ``setdefault``: TensorFlow memory growth is enabled and JAX GPU
13
+ preallocation is disabled. An environment value supplied by the host is never
14
+ overwritten. Hosts that import optional frameworks themselves should call
15
+ ``MemoryType.prepare_import()`` first. ArrayBridge warns when it encounters an
16
+ already-loaded framework whose import-time defaults were absent.
17
+
18
+ JAX float64 output requires x64 mode to be enabled before import. ArrayBridge
19
+ raises instead of silently returning float32 when a caller requests float64
20
+ while that capability is disabled.
21
+
22
+ Decorated GPU callables may execute in a thread-local framework stream and may
23
+ retry supported OOM failures when ``oom_recovery=True``. Decorators do not
24
+ select a device and do not convert an incompatible input.
25
+
26
+ ``cleanup_all_gpu_frameworks(device_id=None)`` invokes allocator cleanup only
27
+ for loaded frameworks that declare a real cleanup leaf. Supplying ``device_id``
28
+ targets that framework-local identifier. Cleanup never imports an absent
29
+ framework. JAX compilation-cache clearing is not presented as GPU-memory
30
+ cleanup. The application decides when values are no longer live and cleanup is
31
+ appropriate.
32
+
33
+ ArrayBridge exposes mechanisms, not scheduling policy. Worker counts, device
34
+ assignment, retry scope, and concurrency limits belong to the host runtime.
@@ -26,10 +26,10 @@ Framework names
26
26
 
27
27
  The public conversion and stack APIs use string values: ``numpy``, ``cupy``,
28
28
  ``torch``, ``tensorflow``, ``jax``, and ``pyclesperanto``. ``convert_memory``
29
- also accepts a ``MemoryType`` member for ``target_type``, but callers should
30
- prefer strings consistently because ``source_type`` and stack utilities require
31
- them. Availability depends on optional dependencies and hardware. ArrayBridge
32
- never silently substitutes a different framework for an invalid target.
29
+ accepts the canonical ``MemoryType`` member at both its source and target
30
+ boundaries. Availability depends on optional dependencies and hardware.
31
+ ArrayBridge never silently substitutes a different framework for an invalid
32
+ target.
33
33
 
34
34
  Applications should carry these values through typed configuration or plans,
35
35
  not infer them from function names.
@@ -1,10 +1,11 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ # Hatchling 1.32 emits Core Metadata 2.5, which Twine 6.2 rejects.
3
+ requires = ["hatchling>=1.28,<1.32"]
3
4
  build-backend = "hatchling.build"
4
5
 
5
6
  [project]
6
7
  name = "arraybridge"
7
- version = "0.2.11"
8
+ version = "0.3.1"
8
9
  description = "Unified API for NumPy, CuPy, PyTorch, TensorFlow, JAX, and pyclesperanto with automatic memory type conversion"
9
10
  authors = [{name = "Tristan Simas", email = "tristan.simas@mail.mcgill.ca"}]
10
11
  license = {text = "MIT"}
@@ -71,6 +72,9 @@ all = [
71
72
  dev = [
72
73
  "pytest>=7.0",
73
74
  "pytest-cov>=4.0",
75
+ "build>=1.0",
76
+ "twine>=5.0",
77
+ "packaging>=23.0",
74
78
  "black>=23.0",
75
79
  "ruff>=0.1.0",
76
80
  "mypy>=1.0",
@@ -119,6 +123,12 @@ select = ["E", "F", "I", "N", "W", "UP"]
119
123
 
120
124
  [tool.mypy]
121
125
  python_version = "3.10"
126
+ follow_imports_for_stubs = true
127
+ ignore_missing_imports = true
122
128
  warn_return_any = true
123
129
  warn_unused_configs = true
124
130
  disallow_untyped_defs = false # Too strict for scientific code
131
+
132
+ [[tool.mypy.overrides]]
133
+ module = "numpy.*"
134
+ follow_imports = "skip"