scitex-storage 0.2.0__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 (152) hide show
  1. scitex_storage-0.2.0/.github/ci/build-in-sif.sh +72 -0
  2. scitex_storage-0.2.0/.github/ci/exec-in-sif.sh +53 -0
  3. scitex_storage-0.2.0/.github/ci/publish-in-sif.sh +113 -0
  4. scitex_storage-0.2.0/.github/ci/run-in-sif.sh +90 -0
  5. scitex_storage-0.2.0/.github/workflows/ci.yml +73 -0
  6. scitex_storage-0.2.0/.github/workflows/cla.yml +26 -0
  7. scitex_storage-0.2.0/.github/workflows/pypi-publish-and-github-release-on-tag.yml +191 -0
  8. scitex_storage-0.2.0/.gitignore +43 -0
  9. scitex_storage-0.2.0/.readthedocs.yaml +22 -0
  10. scitex_storage-0.2.0/.scitex/dev/cli-audit-dict.yaml +10 -0
  11. scitex_storage-0.2.0/.scitex/dev/config.yaml +4 -0
  12. scitex_storage-0.2.0/CHANGELOG.md +90 -0
  13. scitex_storage-0.2.0/CLA.md +80 -0
  14. scitex_storage-0.2.0/CONTRIBUTING.md +69 -0
  15. scitex_storage-0.2.0/LICENSE +660 -0
  16. scitex_storage-0.2.0/PKG-INFO +444 -0
  17. scitex_storage-0.2.0/README.md +397 -0
  18. scitex_storage-0.2.0/codecov.yml +23 -0
  19. scitex_storage-0.2.0/docs/assets/images/scitex-icon-navy-inverted.png +0 -0
  20. scitex_storage-0.2.0/docs/assets/images/scitex-logo-blue-cropped.png +0 -0
  21. scitex_storage-0.2.0/docs/sphinx/api/scitex_storage.rst +7 -0
  22. scitex_storage-0.2.0/docs/sphinx/conf.py +109 -0
  23. scitex_storage-0.2.0/docs/sphinx/index.rst +29 -0
  24. scitex_storage-0.2.0/docs/sphinx/installation.rst +23 -0
  25. scitex_storage-0.2.0/docs/sphinx/quickstart.rst +7 -0
  26. scitex_storage-0.2.0/docs/sphinx/requirements.txt +5 -0
  27. scitex_storage-0.2.0/examples/quickstart.py +48 -0
  28. scitex_storage-0.2.0/pyproject.toml +107 -0
  29. scitex_storage-0.2.0/src/scitex_storage/__init__.py +123 -0
  30. scitex_storage-0.2.0/src/scitex_storage/__main__.py +12 -0
  31. scitex_storage-0.2.0/src/scitex_storage/_archive.py +341 -0
  32. scitex_storage-0.2.0/src/scitex_storage/_cli/__init__.py +136 -0
  33. scitex_storage-0.2.0/src/scitex_storage/_cli/_archive_cmd.py +188 -0
  34. scitex_storage-0.2.0/src/scitex_storage/_cli/_compat.py +121 -0
  35. scitex_storage-0.2.0/src/scitex_storage/_cli/_duplicates_cmd.py +60 -0
  36. scitex_storage-0.2.0/src/scitex_storage/_cli/_images_cmd.py +90 -0
  37. scitex_storage-0.2.0/src/scitex_storage/_cli/_introspect.py +59 -0
  38. scitex_storage-0.2.0/src/scitex_storage/_cli/_mcp_commands.py +62 -0
  39. scitex_storage-0.2.0/src/scitex_storage/_cli/_scan_cmd.py +110 -0
  40. scitex_storage-0.2.0/src/scitex_storage/_cli/_sweep_cmd.py +142 -0
  41. scitex_storage-0.2.0/src/scitex_storage/_duplicates.py +118 -0
  42. scitex_storage-0.2.0/src/scitex_storage/_images.py +237 -0
  43. scitex_storage-0.2.0/src/scitex_storage/_report.py +443 -0
  44. scitex_storage-0.2.0/src/scitex_storage/_scan.py +324 -0
  45. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_modules/index.html +142 -0
  46. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_modules/scitex_storage/_scan.html +376 -0
  47. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/api/scitex_storage.rst.txt +7 -0
  48. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/index.rst.txt +29 -0
  49. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/installation.rst.txt +23 -0
  50. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/quickstart.rst.txt +7 -0
  51. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/_sphinx_javascript_frameworks_compat.js +123 -0
  52. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/base-stemmer.js +476 -0
  53. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/basic.css +906 -0
  54. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/check-solid.svg +4 -0
  55. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/clipboard.min.js +7 -0
  56. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copy-button.svg +5 -0
  57. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copybutton.css +94 -0
  58. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copybutton.js +248 -0
  59. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copybutton_funcs.js +73 -0
  60. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/badge_only.css +1 -0
  61. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  62. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  63. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  64. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  65. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.eot +0 -0
  66. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.svg +2671 -0
  67. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  68. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.woff +0 -0
  69. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  70. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold-italic.woff +0 -0
  71. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  72. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold.woff +0 -0
  73. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold.woff2 +0 -0
  74. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal-italic.woff +0 -0
  75. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  76. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal.woff +0 -0
  77. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal.woff2 +0 -0
  78. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/theme.css +4 -0
  79. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/doctools.js +150 -0
  80. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/documentation_options.js +13 -0
  81. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/english-stemmer.js +1066 -0
  82. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/file.png +0 -0
  83. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.eot +0 -0
  84. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.ttf +0 -0
  85. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.woff +0 -0
  86. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.woff2 +0 -0
  87. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  88. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  89. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  90. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  91. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.eot +0 -0
  92. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.ttf +0 -0
  93. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.woff +0 -0
  94. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.woff2 +0 -0
  95. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.eot +0 -0
  96. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.ttf +0 -0
  97. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.woff +0 -0
  98. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.woff2 +0 -0
  99. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  100. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  101. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  102. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  103. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  104. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  105. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  106. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  107. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/jquery.js +2 -0
  108. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/js/badge_only.js +1 -0
  109. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/js/theme.js +1 -0
  110. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/js/versions.js +228 -0
  111. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/language_data.js +13 -0
  112. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/minus.png +0 -0
  113. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/plus.png +0 -0
  114. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/pygments.css +75 -0
  115. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/searchtools.js +693 -0
  116. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/sphinx_highlight.js +159 -0
  117. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/api/scitex_storage.html +314 -0
  118. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/genindex.html +292 -0
  119. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/index.html +185 -0
  120. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/installation.html +170 -0
  121. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/objects.inv +0 -0
  122. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/py-modindex.html +164 -0
  123. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/quickstart.html +203 -0
  124. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/search.html +161 -0
  125. scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/searchindex.js +1 -0
  126. scitex_storage-0.2.0/src/scitex_storage/_sweep.py +384 -0
  127. scitex_storage-0.2.0/src/scitex_storage/_system_deps.py +91 -0
  128. scitex_storage-0.2.0/tests/__init__.py +0 -0
  129. scitex_storage-0.2.0/tests/conftest.py +6 -0
  130. scitex_storage-0.2.0/tests/develop/__init__.py +7 -0
  131. scitex_storage-0.2.0/tests/develop/test_audit.py +26 -0
  132. scitex_storage-0.2.0/tests/examples/__init__.py +0 -0
  133. scitex_storage-0.2.0/tests/examples/test_quickstart.py +49 -0
  134. scitex_storage-0.2.0/tests/integration/__init__.py +0 -0
  135. scitex_storage-0.2.0/tests/integration/test_cross_package_imports.py +91 -0
  136. scitex_storage-0.2.0/tests/scitex_storage/__init__.py +0 -0
  137. scitex_storage-0.2.0/tests/scitex_storage/_cli/__init__.py +0 -0
  138. scitex_storage-0.2.0/tests/scitex_storage/_cli/test___init__.py +58 -0
  139. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__archive_cmd.py +145 -0
  140. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__compat.py +45 -0
  141. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__duplicates_cmd.py +106 -0
  142. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__images_cmd.py +144 -0
  143. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__introspect.py +34 -0
  144. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__mcp_commands.py +34 -0
  145. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__scan_cmd.py +146 -0
  146. scitex_storage-0.2.0/tests/scitex_storage/_cli/test__sweep_cmd.py +197 -0
  147. scitex_storage-0.2.0/tests/scitex_storage/test__archive.py +645 -0
  148. scitex_storage-0.2.0/tests/scitex_storage/test__duplicates.py +212 -0
  149. scitex_storage-0.2.0/tests/scitex_storage/test__images.py +309 -0
  150. scitex_storage-0.2.0/tests/scitex_storage/test__report.py +182 -0
  151. scitex_storage-0.2.0/tests/scitex_storage/test__scan.py +313 -0
  152. scitex_storage-0.2.0/tests/scitex_storage/test__sweep.py +432 -0
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # Runs INSIDE the reused scitex-ci SIF (apptainer exec — invoked via
3
+ # exec-in-sif.sh). Builds scitex-storage's wheel + sdist into ./dist/.
4
+ # Adapted from scitex-ssh's .github/ci/build-in-sif.sh (the fleet's
5
+ # established sibling convention) — only the TMPDIR label and echoed package
6
+ # name changed; logic otherwise unchanged.
7
+ #
8
+ # WHY build in the SIF: the self-hosted Spartan runner has no Python on the
9
+ # bare node (the whole reason the old `actions/setup-python@v5` step failed:
10
+ # "version 3.x not found for this OS"). The SIF bakes python 3.11/3.12/3.13 +
11
+ # pip + uv at /opt/venv-<ver>, exactly like the working pytest-matrix CI.
12
+ #
13
+ # `python -m build` needs the `build` frontend, which is NOT baked in the SIF
14
+ # (only scitex-dev[all,dev] deps are). Mirror run-in-sif.sh: install `build`
15
+ # into a writable --target on node-local /tmp and put it on PYTHONPATH. The
16
+ # SIF's /opt/venv-* are root-owned + RO and the compute-node HOME is RO inside
17
+ # the container, so a normal install fails Permission denied — a --target on
18
+ # writable scratch sidesteps both.
19
+ #
20
+ # Fail-loud (operator directive): a missing interpreter or a failed build is a
21
+ # HARD error, never a silent fallback.
22
+ set -euo pipefail
23
+
24
+ V="${1:-3.12}"
25
+ VENV="/opt/venv-$V"
26
+ PY="$VENV/bin/python"
27
+ test -x "$PY" || {
28
+ echo "::error::baked python missing in $VENV — rebuild the SIF: scitex-container apptainer build ci-cpu"
29
+ exit 1
30
+ }
31
+
32
+ export LC_ALL=C.UTF-8 LANG=C.UTF-8
33
+
34
+ # Writable scratch (the runner's TMPDIR=~/.cache/tmp is a host path that does
35
+ # NOT resolve inside the container). Node-local /tmp is writable + ephemeral.
36
+ TMPDIR="/tmp/build-scitex_storage-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-$V"
37
+ export TMPDIR
38
+ rm -rf "$TMPDIR"
39
+ mkdir -p "$TMPDIR/site" "$TMPDIR/uv-cache"
40
+
41
+ # The compute-node $HOME is RO inside the container — point every cache the
42
+ # installer might touch at the writable scratch (else uv/pip die creating
43
+ # ~/.cache).
44
+ export UV_CACHE_DIR="$TMPDIR/uv-cache"
45
+ export XDG_CACHE_HOME="$TMPDIR"
46
+ export PIP_CACHE_DIR="$TMPDIR/pip-cache"
47
+
48
+ # A VIRTUAL_ENV leaked from the runner profile (~/.env-3.11) is a broken
49
+ # symlink in here; unset it so no tool follows it.
50
+ unset VIRTUAL_ENV || true
51
+
52
+ export PATH="$VENV/bin:$PATH"
53
+ echo "build: py=$("$PY" -V) target=$TMPDIR/site"
54
+
55
+ # Install the PEP 517 build frontend into the writable target (uv fast path,
56
+ # pip safety net), then build with it. Clean dist/ first so only the freshly
57
+ # built artifacts are uploaded.
58
+ uv pip install --python "$PY" --target="$TMPDIR/site" build ||
59
+ "$PY" -m pip install --target="$TMPDIR/site" build
60
+
61
+ export PYTHONPATH="$TMPDIR/site${PYTHONPATH:+:$PYTHONPATH}"
62
+
63
+ rm -rf dist
64
+ "$PY" -m build --outdir dist
65
+
66
+ echo "=== built artifacts ==="
67
+ ls -l dist
68
+ # fail-loud: refuse to continue the pipeline with an empty dist/.
69
+ test -n "$(ls -A dist 2>/dev/null)" || {
70
+ echo "::error::python -m build produced no artifacts in dist/"
71
+ exit 1
72
+ }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ # Outer apptainer-exec wrapper for scitex-storage's self-hosted (Spartan) CI.
3
+ # Copied verbatim from scitex-ssh's .github/ci/exec-in-sif.sh (the fleet's
4
+ # established sibling convention) — no package-specific content in this file.
5
+ #
6
+ # Runs ON THE RUNNER (outside the SIF). Resolves the apptainer shim + SIF image
7
+ # from the repo Actions Variables, then `apptainer exec`s the SIF and hands off
8
+ # to an INNER script (run inside the container). Keeps every workflow job's YAML
9
+ # down to one line — `bash .github/ci/exec-in-sif.sh <inner-script> [args...]` —
10
+ # and concentrates all the HPC/SIF plumbing (shim PATH, ~-expansion, scratch,
11
+ # binds) in one version-controlled place.
12
+ #
13
+ # Required env (set by the workflow from repo Actions Variables):
14
+ # SCITEX_CI_APPTAINER path to the apptainer shim (e.g. ~/.env-3.11/bin/apptainer)
15
+ # SCITEX_CI_SIF path to the CI SIF image (e.g. ~/.scitex/dev/containers/ci-cpu.sif)
16
+ #
17
+ # Usage:
18
+ # bash .github/ci/exec-in-sif.sh run-in-sif.sh 3.12
19
+ #
20
+ # Fail-loud (operator directive): a missing shim or SIF is a HARD error — never
21
+ # a silent fallback to a bare-runner install.
22
+ set -euo pipefail
23
+
24
+ INNER="${1:?inner script name required (relative to .github/ci/)}"
25
+ shift || true
26
+
27
+ # The runner's job shell is --noprofile --norc (no Lmod), so the apptainer shim
28
+ # must be put on PATH explicitly; it execs the real Apptainer binary directly.
29
+ # ~-expand the Actions-Variable paths: a quoted "~/…" is NOT tilde-expanded by
30
+ # the shell, so substitute a leading ~ with $HOME ourselves.
31
+ APPTAINER="${SCITEX_CI_APPTAINER:?SCITEX_CI_APPTAINER not set (repo Actions Variable)}"
32
+ SIF="${SCITEX_CI_SIF:?SCITEX_CI_SIF not set (repo Actions Variable)}"
33
+ APPTAINER="${APPTAINER/#\~/$HOME}"
34
+ SIF="${SIF/#\~/$HOME}"
35
+ export PATH="$HOME/.env-3.11/bin:$PATH"
36
+
37
+ [ -x "$APPTAINER" ] || {
38
+ echo "::error::apptainer shim not executable at $APPTAINER"
39
+ exit 1
40
+ }
41
+ [ -f "$SIF" ] || {
42
+ echo "::error::CI SIF missing at $SIF — rebuild it: scitex-container apptainer build ci-cpu"
43
+ exit 1
44
+ }
45
+
46
+ # apptainer scratch on the shared FS — keeps HOME clean.
47
+ export APPTAINER_TMPDIR="/data/gpfs/projects/punim0264/ywatanabe/ci/apptainer-tmp"
48
+ mkdir -p "$APPTAINER_TMPDIR"
49
+
50
+ # --bind punim0264: $HOME/.scitex is a symlink into punim0264; bind it so the
51
+ # symlink resolves inside the container. --pwd "$PWD" keeps the checkout as cwd.
52
+ exec "$APPTAINER" exec --pwd "$PWD" --bind /data/gpfs/projects/punim0264 \
53
+ "$SIF" bash ".github/ci/$INNER" "$@"
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ # Runs INSIDE the reused scitex-ci SIF (apptainer exec — invoked via
3
+ # exec-in-sif.sh). Publishes ./dist/* to PyPI via MANUAL OIDC Trusted
4
+ # Publishing, then twine upload.
5
+ # Adapted from scitex-ssh's .github/ci/publish-in-sif.sh (the fleet's
6
+ # established sibling convention) — only the TMPDIR label and package name in
7
+ # the trust-config comment / final echo changed; logic otherwise unchanged.
8
+ #
9
+ # WHY manual OIDC (not pypa/gh-action-pypi-publish): that action is a Docker
10
+ # container action. Self-hosted Spartan compute nodes have NO Docker, so the
11
+ # action cannot run there. PyPI Trusted Publishing is just an OIDC token
12
+ # exchange over plain HTTPS, so we do it by hand:
13
+ #
14
+ # 1. Ask the GitHub Actions OIDC provider for a JWT with audience=pypi,
15
+ # using the per-job ACTIONS_ID_TOKEN_REQUEST_{TOKEN,URL} env vars (present
16
+ # because the publish job declares `permissions: id-token: write`).
17
+ # apptainer exec (no --cleanenv) passes those host env vars into the SIF.
18
+ # 2. Exchange that JWT at PyPI's mint-token endpoint for a short-lived,
19
+ # scope-limited PyPI API token.
20
+ # 3. twine upload dist/* with TWINE_USERNAME=__token__ and that minted token.
21
+ #
22
+ # REQUIRES a Trusted Publisher to already be configured on PyPI for
23
+ # (project=scitex-storage, owner=scitex-ai, repo=scitex-storage,
24
+ # workflow=release.yml, environment=pypi). This is a ONE-TIME, PyPI-side
25
+ # (pypi.org/manage/) operator action — NOT something this script, or any CI
26
+ # job, can self-configure. If it is not yet registered, mint-token below
27
+ # returns an empty token and this script fails loud with PyPI's raw error body.
28
+ #
29
+ # curl, python and (after a --target install) twine all live in the SIF.
30
+ #
31
+ # Fail-loud (operator directive): every step asserts non-empty output and
32
+ # `set -euo pipefail`; any failure is a HARD error with the exact cause, never
33
+ # a silent skip.
34
+ set -euo pipefail
35
+
36
+ V="${1:-3.12}"
37
+ VENV="/opt/venv-$V"
38
+ PY="$VENV/bin/python"
39
+ test -x "$PY" || {
40
+ echo "::error::baked python missing in $VENV — rebuild the SIF: scitex-container apptainer build ci-cpu"
41
+ exit 1
42
+ }
43
+
44
+ export LC_ALL=C.UTF-8 LANG=C.UTF-8
45
+
46
+ # dist/ must already hold the artifacts (downloaded by the publish job before
47
+ # this script runs). Fail loud if empty.
48
+ if [ ! -d dist ] || [ -z "$(ls -A dist 2>/dev/null)" ]; then
49
+ echo "::error::dist/ is empty — nothing to publish (download the build artifact first)"
50
+ exit 1
51
+ fi
52
+ echo "=== dist to publish ==="
53
+ ls -l dist
54
+
55
+ # --- writable scratch (compute-node HOME is RO inside the container) ---
56
+ TMPDIR="/tmp/publish-scitex_storage-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-$V"
57
+ export TMPDIR
58
+ rm -rf "$TMPDIR"
59
+ mkdir -p "$TMPDIR/site" "$TMPDIR/uv-cache"
60
+ export UV_CACHE_DIR="$TMPDIR/uv-cache"
61
+ export XDG_CACHE_HOME="$TMPDIR"
62
+ export PIP_CACHE_DIR="$TMPDIR/pip-cache"
63
+ unset VIRTUAL_ENV || true
64
+ export PATH="$VENV/bin:$PATH"
65
+
66
+ # --- step 1: request the OIDC JWT (audience=pypi) from GitHub ---
67
+ : "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:?ACTIONS_ID_TOKEN_REQUEST_TOKEN not set — the publish job needs 'permissions: id-token: write'}"
68
+ : "${ACTIONS_ID_TOKEN_REQUEST_URL:?ACTIONS_ID_TOKEN_REQUEST_URL not set — the publish job needs 'permissions: id-token: write'}"
69
+
70
+ echo "=== minting OIDC JWT (audience=pypi) ==="
71
+ JWT="$(curl -fsS \
72
+ -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
73
+ "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=pypi" |
74
+ "$PY" -c 'import sys,json; print(json.load(sys.stdin)["value"])')"
75
+ test -n "$JWT" || {
76
+ echo "::error::OIDC JWT request returned an empty token"
77
+ exit 1
78
+ }
79
+ echo "OIDC JWT obtained (length=${#JWT})"
80
+
81
+ # --- step 2: exchange the JWT for a short-lived PyPI API token ---
82
+ echo "=== exchanging JWT at PyPI mint-token endpoint ==="
83
+ MINT_RESP="$(curl -sS -X POST https://pypi.org/_/oidc/mint-token \
84
+ -d "{\"token\":\"${JWT}\"}")"
85
+ MINTED="$(printf '%s' "$MINT_RESP" |
86
+ "$PY" -c 'import sys,json; d=json.load(sys.stdin); print(d.get("token",""))')"
87
+ if [ -z "$MINTED" ]; then
88
+ # Surface PyPI's error body VERBATIM (the JWT is NOT echoed) so a trust
89
+ # misconfiguration is diagnosable — this is the decisive failure mode for
90
+ # the fleet. Print the raw response unconditionally (most reliable on an
91
+ # error path); pretty-print is best-effort on top.
92
+ echo "::error::PyPI mint-token returned no token."
93
+ echo "--- PyPI mint-token response body (raw) ---"
94
+ printf '%s\n' "$MINT_RESP"
95
+ echo "--- (pretty, best-effort) ---"
96
+ printf '%s' "$MINT_RESP" |
97
+ "$PY" -c 'import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))' \
98
+ 2>/dev/null || true
99
+ exit 1
100
+ fi
101
+ echo "PyPI token minted (length=${#MINTED})"
102
+
103
+ # --- step 3: install twine into the writable target, then upload ---
104
+ echo "=== installing twine (--target) ==="
105
+ uv pip install --python "$PY" --target="$TMPDIR/site" twine ||
106
+ "$PY" -m pip install --target="$TMPDIR/site" twine
107
+ export PYTHONPATH="$TMPDIR/site${PYTHONPATH:+:$PYTHONPATH}"
108
+
109
+ echo "=== twine upload dist/* ==="
110
+ TWINE_USERNAME="__token__" TWINE_PASSWORD="$MINTED" \
111
+ "$PY" -m twine upload --non-interactive --disable-progress-bar dist/*
112
+
113
+ echo "PUBLISH-OK: scitex-storage dist/* uploaded to PyPI via manual OIDC trusted publishing"
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # Runs INSIDE the reused scitex-ci SIF (apptainer exec). $1 = python version.
3
+ # Adapted from scitex-ssh's .github/ci/run-in-sif.sh (the fleet's established
4
+ # sibling convention) — package name / import path / cov target swapped for
5
+ # scitex-storage; logic otherwise unchanged.
6
+ #
7
+ # WHY a layered install (not the bare PYTHONPATH=src trick scitex-dev uses):
8
+ # the shared ci-cpu.sif bakes scitex-dev[all,dev] DEPS, NOT scitex-storage's.
9
+ # So we install THIS checkout + its [all,dev] extras (WITH dependency
10
+ # resolution) into a writable --target dir and prepend that on PYTHONPATH. The
11
+ # SIF still supplies the heavy shared base (pip/uv, the python interpreters,
12
+ # scitex-dev's deps), so only scitex-storage's own thin dep set is fetched per
13
+ # run.
14
+ #
15
+ # --target (not a plain `-e .`): the SIF's /opt/venv-* are root-owned + RO and
16
+ # the HPC compute-node HOME is RO inside the container, so a normal site install
17
+ # fails Permission denied. A writable target on node-local /tmp sidesteps both.
18
+ #
19
+ # Fail-loud: a missing interpreter or a failed install is a hard error.
20
+ set -euo pipefail
21
+
22
+ V="${1:?python version arg required (3.11/3.12/3.13)}"
23
+ VENV="/opt/venv-$V"
24
+ test -x "$VENV/bin/python" || {
25
+ echo "::error::baked python missing in $VENV — rebuild the SIF: scitex-container apptainer build ci-cpu"
26
+ exit 1
27
+ }
28
+
29
+ export LC_ALL=C.UTF-8 LANG=C.UTF-8
30
+
31
+ # Real writable scratch. The runner profile exports TMPDIR=~/.cache/tmp, a host
32
+ # path that does NOT resolve inside the container; tests (tmp_path) and the
33
+ # install target both need a working, writable tmp. Node-local /tmp is writable
34
+ # + ephemeral and per-version-isolated so concurrent matrix legs don't collide.
35
+ export TMPDIR="/tmp/ci-scitex_storage-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-$V"
36
+ rm -rf "$TMPDIR"
37
+ mkdir -p "$TMPDIR/site" "$TMPDIR/uv-cache"
38
+
39
+ # The HPC compute-node $HOME is READ-ONLY inside the container, so uv/pip cannot
40
+ # create their default caches under ~/.cache — point them at the writable
41
+ # scratch instead (else `uv pip install` dies: "failed to create directory
42
+ # ~/.cache/uv: File exists / read-only").
43
+ export UV_CACHE_DIR="$TMPDIR/uv-cache"
44
+ export XDG_CACHE_HOME="$TMPDIR"
45
+ export PIP_CACHE_DIR="$TMPDIR/pip-cache"
46
+
47
+ # A VIRTUAL_ENV leaked from the runner profile (~/.env-3.11) is a broken symlink
48
+ # in here; unset it so no tool (uv, pip) tries to follow it.
49
+ unset VIRTUAL_ENV || true
50
+
51
+ # venv bin on PATH (this matrix leg's python3 + pip); PYTHONPATH points at the
52
+ # writable target so imports + coverage use the freshly-installed checkout.
53
+ export PATH="$VENV/bin:$PATH"
54
+
55
+ echo "py=$("$VENV/bin/python" -V) target=$TMPDIR/site"
56
+
57
+ # Install scitex-storage + its [all,dev] extras WITH deps into the writable
58
+ # target. Fallback chain so a packaging hiccup in an optional extra doesn't
59
+ # strand CI: [all,dev] → [dev] → bare. uv first (fast resolver), pip as a
60
+ # final safety net.
61
+ uv pip install --python "$VENV/bin/python" --target="$TMPDIR/site" -e ".[all,dev]" ||
62
+ uv pip install --python "$VENV/bin/python" --target="$TMPDIR/site" -e ".[dev]" ||
63
+ uv pip install --python "$VENV/bin/python" --target="$TMPDIR/site" -e "." ||
64
+ pip install --target="$TMPDIR/site" -e ".[dev]"
65
+
66
+ export PYTHONPATH="$TMPDIR/site:$PWD/src${PYTHONPATH:+:$PYTHONPATH}"
67
+
68
+ # Parallelise with pytest-xdist if available (no-op fallback to plain -q if
69
+ # pytest-xdist isn't in scitex-storage's [dev] extras). Worker count: use ALL
70
+ # cores (each matrix leg runs on its own dedicated self-hosted node, no
71
+ # co-tenant). Floor 4.
72
+ NPROC="$(nproc 2>/dev/null || echo 4)"
73
+ WORKERS=$NPROC
74
+ [ "$WORKERS" -lt 4 ] && WORKERS=4
75
+ echo "xdist workers=$WORKERS (nproc=$NPROC)"
76
+
77
+ XDIST_ARGS=()
78
+ if python -c "import xdist" 2>/dev/null; then
79
+ XDIST_ARGS=(-n "$WORKERS" --dist load)
80
+ else
81
+ echo "pytest-xdist not importable — running single-process"
82
+ fi
83
+
84
+ # nice -n 19 ionice -c 3: lowest CPU + idle I/O priority so CI yields to any
85
+ # higher-priority process if the node is ever shared. exec replaces the shell so
86
+ # signals/exit code propagate to the runner step.
87
+ exec nice -n 19 ionice -c 3 \
88
+ python -m pytest tests/ "${XDIST_ARGS[@]}" -q \
89
+ --cov=src/scitex_storage --cov-report=xml --cov-report=term \
90
+ -p no:cacheprovider
@@ -0,0 +1,73 @@
1
+ name: ci
2
+
3
+ # Org-level reusable-workflow caller (scitex-ai/.github, fleet standard as of
4
+ # 2026-07-10 — see scitex-container / scitex-todo for the same pattern).
5
+ # Four thin `uses:` calls into scitex-ai/.github reusable workflows instead
6
+ # of inlining the job bodies; centrally maintained.
7
+ #
8
+ # Job ids are chosen so the resulting required-status-check context is
9
+ # predictable: "<job id> / <reusable job name>".
10
+
11
+ on:
12
+ pull_request:
13
+ branches: [main, develop]
14
+ push:
15
+ branches: [main, develop]
16
+ schedule:
17
+ - cron: "0 17 * * *" # nightly 17:00 UTC (~02:00 JST), off-peak
18
+ workflow_dispatch:
19
+
20
+ concurrency:
21
+ group: ci-${{ github.workflow }}-${{ github.ref }}
22
+ cancel-in-progress: true
23
+
24
+ jobs:
25
+ pytest-matrix:
26
+ # Required-status-check context: "pytest-matrix / pytest-matrix-on-ubuntu-py<X.YY>"
27
+ uses: scitex-ai/.github/.github/workflows/pytest-matrix.yml@main
28
+ secrets: inherit
29
+
30
+ import-smoke:
31
+ # Required-status-check context: "import-smoke / import-smoke-on-ubuntu-py3-12"
32
+ uses: scitex-ai/.github/.github/workflows/import-smoke.yml@main
33
+ secrets: inherit
34
+
35
+ quality-audit:
36
+ # Required-status-check context: "quality-audit / audit"
37
+ uses: scitex-ai/.github/.github/workflows/quality-audit.yml@main
38
+ secrets: inherit
39
+
40
+ rtd-sphinx-build:
41
+ # Required-status-check context: "rtd-sphinx-build / docs-sphinx"
42
+ uses: scitex-ai/.github/.github/workflows/rtd-sphinx-build.yml@main
43
+ secrets: inherit
44
+
45
+ # Self-contained (not a shared-org `uses:` call, unlike the four jobs
46
+ # above) -- `scan` shells out to `fd` and `find-duplicates` to `fclones`
47
+ # (see src/scitex_storage/_scan.py / _duplicates.py), and `pytest-matrix`
48
+ # above doesn't install either binary, so the tests that exercise them
49
+ # for real stay skipped there. This job installs both (fast: apt for
50
+ # `fd-find`, the upstream release .deb for `fclones` -- no `cargo build`
51
+ # in CI) and reruns the suite so the real integration paths get CI
52
+ # coverage too.
53
+ fd-fclones-integration:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ - uses: actions/setup-python@v5
58
+ with:
59
+ python-version: "3.12"
60
+ - name: Install fd + fclones
61
+ run: |
62
+ sudo apt-get update -y
63
+ sudo apt-get install -y fd-find
64
+ FCLONES_VERSION=0.35.0
65
+ curl -fsSL -o /tmp/fclones.deb \
66
+ "https://github.com/pkolaczk/fclones/releases/download/v${FCLONES_VERSION}/fclones_${FCLONES_VERSION}-1_amd64.deb"
67
+ sudo apt-get install -y /tmp/fclones.deb
68
+ fdfind --version
69
+ fclones --version
70
+ - name: Install scitex-storage[dev]
71
+ run: pip install -e '.[dev]'
72
+ - name: Run tests with fd/fclones on PATH
73
+ run: pytest "$GITHUB_WORKSPACE/tests/" -q
@@ -0,0 +1,26 @@
1
+ name: CLA Assistant
2
+
3
+ # Org-level reusable-workflow caller (scitex-ai/.github, fleet standard).
4
+ # Defaults (default_branch=main, owner_allowlist="bot*,ywatanabe1989",
5
+ # owner_login=ywatanabe1989) already match this repo — no `with:` overrides
6
+ # needed.
7
+
8
+ on:
9
+ issue_comment:
10
+ types: [created]
11
+ pull_request_target:
12
+ types: [opened, closed, synchronize]
13
+ push:
14
+ branches: [main, develop]
15
+ tags: ['v*']
16
+
17
+ permissions:
18
+ actions: write
19
+ contents: write
20
+ pull-requests: write
21
+ statuses: write
22
+
23
+ jobs:
24
+ call:
25
+ uses: scitex-ai/.github/.github/workflows/cla.yml@main
26
+ secrets: inherit
@@ -0,0 +1,191 @@
1
+ name: release
2
+
3
+ # Tag-driven release pipeline — SELF-HOSTED (Spartan CPU) via the scitex-ci SIF.
4
+ # Copied verbatim (structure + comments) from scitex-ssh's
5
+ # .github/workflows/pypi-publish-and-github-release-on-tag.yml — the fleet's
6
+ # established sibling convention for a scitex-* package's first (and every
7
+ # subsequent) PyPI release — and adapted for scitex-storage (package name,
8
+ # PyPI project url). See that repo for the canonical reference.
9
+ #
10
+ # The intended operator flow is:
11
+ #
12
+ # git tag vX.Y.Z && git push origin vX.Y.Z
13
+ #
14
+ # Everything else fires automatically:
15
+ #
16
+ # 1. test — matrix pytest INSIDE the SIF. Must pass or the pipeline
17
+ # halts; nothing is published until tests are green on the
18
+ # tagged commit.
19
+ # 2. build — wheel + sdist built INSIDE the SIF, uploaded as artifact.
20
+ # 3. publish — PyPI via MANUAL OIDC trusted publishing INSIDE the SIF.
21
+ # 4. release — GitHub Release with CHANGELOG notes + artifacts.
22
+ #
23
+ # develop→main promotion is a DELIBERATE, separate PR (operator policy);
24
+ # this pipeline never auto-syncs main.
25
+ #
26
+ # WHY the SIF (operator standardization 2026-06-18, fleet canary 2026-06-21):
27
+ # the Spartan compute runners have NO Python on the bare node, so the old
28
+ # `actions/setup-python@v5` step failed at init ("version 3.x not found for
29
+ # this OS"). The working pytest-matrix CI runs INSIDE the pre-built, reused
30
+ # scitex-ci SIF (`apptainer exec ci-cpu.sif`), which bakes python 3.11/3.12/
31
+ # 3.13 + pip + uv + fd at /opt/venv-<ver>. This release pipeline mirrors that:
32
+ # - test/build run `bash .github/ci/exec-in-sif.sh <inner>.sh <pyver>`.
33
+ # - publish CANNOT use pypa/gh-action-pypi-publish (a Docker action; the
34
+ # compute nodes have no Docker), so it does MANUAL OIDC trusted publishing
35
+ # inside the SIF (.github/ci/publish-in-sif.sh): GitHub OIDC JWT →
36
+ # PyPI mint-token → twine upload.
37
+ # - actions/checkout, upload/download-artifact are JS actions and run fine
38
+ # on the self-hosted runner.
39
+ # fail-loud: a missing SIF/shim/interpreter is a HARD error, never a
40
+ # bare-runner fallback.
41
+ #
42
+ # PREREQUISITE (one-time, PyPI-side, NOT part of this workflow): a Trusted
43
+ # Publisher must be registered on PyPI for
44
+ # (project=scitex-storage, owner=scitex-ai, repo=scitex-storage,
45
+ # workflow=release.yml, environment=pypi) BEFORE the first tag is pushed —
46
+ # this is an operator action on pypi.org/manage/, not something this
47
+ # workflow (or any CI job) can self-configure. See CHANGELOG.md /
48
+ # scitex-todo card scitex-storage-pypi-release for status.
49
+ #
50
+ # runs-on driven by repo Actions Variable CI_RUNS_ON; SIF + apptainer paths
51
+ # come from SCITEX_CI_SIF / SCITEX_CI_APPTAINER (same as pytest-matrix).
52
+ #
53
+ # Manual re-run: the workflow also accepts a `version` input via
54
+ # workflow_dispatch so you can re-publish without re-tagging.
55
+
56
+ on:
57
+ push:
58
+ tags: ['v*']
59
+ workflow_dispatch:
60
+ inputs:
61
+ version:
62
+ description: 'Tag to publish (e.g. v0.2.0) — must already exist on the repo'
63
+ required: true
64
+ type: string
65
+
66
+ env:
67
+ SCITEX_CI_APPTAINER: ${{ vars.SCITEX_CI_APPTAINER }}
68
+ SCITEX_CI_SIF: ${{ vars.SCITEX_CI_SIF }}
69
+
70
+ jobs:
71
+ test:
72
+ runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
73
+ strategy:
74
+ fail-fast: false
75
+ matrix:
76
+ python-version: ["3.11", "3.12", "3.13"]
77
+ steps:
78
+ - name: Resolve version
79
+ id: ver
80
+ run: |
81
+ if [ "${{ github.event_name }}" = "push" ]; then
82
+ v="${GITHUB_REF#refs/tags/}"
83
+ else
84
+ v="${{ inputs.version }}"
85
+ fi
86
+ echo "version=$v" >> "$GITHUB_OUTPUT"
87
+ - uses: actions/checkout@v4
88
+ with:
89
+ ref: ${{ steps.ver.outputs.version }}
90
+ fetch-depth: 0
91
+ # SIF-based self-hosted test run — mirrors pytest-matrix exactly. No
92
+ # setup-python (the bare Spartan node has no Python tool cache; that step
93
+ # failed "version not found for this OS"); no apt fd-install (fd is baked
94
+ # in the SIF). exec-in-sif.sh apptainer-execs the reused ci-cpu.sif and
95
+ # run-in-sif.sh layers THIS (tagged) checkout + [all,dev] into a writable
96
+ # target, then runs pytest. fail-loud on a missing SIF/shim.
97
+ - name: Run tests in the reused CI SIF (apptainer exec — deps layered, not on the bare runner)
98
+ run: bash .github/ci/exec-in-sif.sh run-in-sif.sh ${{ matrix.python-version }}
99
+
100
+ build:
101
+ needs: test
102
+ runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
103
+ outputs:
104
+ version: ${{ steps.ver.outputs.version }}
105
+ steps:
106
+ - name: Resolve version
107
+ id: ver
108
+ run: |
109
+ if [ "${{ github.event_name }}" = "push" ]; then
110
+ v="${GITHUB_REF#refs/tags/}"
111
+ else
112
+ v="${{ inputs.version }}"
113
+ fi
114
+ echo "version=$v" >> "$GITHUB_OUTPUT"
115
+ - uses: actions/checkout@v4
116
+ with:
117
+ ref: ${{ steps.ver.outputs.version }}
118
+ fetch-depth: 0
119
+ # Build INSIDE the SIF (no setup-python on the bare Spartan node).
120
+ # build-in-sif.sh installs the `build` frontend into a writable --target
121
+ # inside the SIF, then `python -m build` → dist/. fail-loud on empty dist.
122
+ - name: Build wheel + sdist in the CI SIF (apptainer exec)
123
+ run: bash .github/ci/exec-in-sif.sh build-in-sif.sh 3.12
124
+ # upload-artifact is a JS action — runs fine on the self-hosted runner.
125
+ - uses: actions/upload-artifact@v4
126
+ with:
127
+ name: dist
128
+ path: dist/
129
+
130
+ publish:
131
+ needs: build
132
+ runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
133
+ environment:
134
+ name: pypi
135
+ url: https://pypi.org/p/scitex-storage
136
+ # id-token: write is what populates ACTIONS_ID_TOKEN_REQUEST_{TOKEN,URL},
137
+ # which publish-in-sif.sh uses to mint the OIDC JWT. Keep it.
138
+ permissions:
139
+ id-token: write
140
+ steps:
141
+ # Need the repo checked out so .github/ci/exec-in-sif.sh +
142
+ # publish-in-sif.sh are on disk (the build job uploaded only dist/).
143
+ - uses: actions/checkout@v4
144
+ - uses: actions/download-artifact@v4
145
+ with:
146
+ name: dist
147
+ path: dist
148
+ # Manual OIDC trusted publishing INSIDE the SIF — pypa/gh-action-pypi-
149
+ # publish is a Docker action and the Spartan compute nodes have no Docker.
150
+ # publish-in-sif.sh: GitHub OIDC JWT (audience=pypi) → PyPI mint-token →
151
+ # twine upload dist/*. apptainer exec (no --cleanenv) passes the
152
+ # ACTIONS_ID_TOKEN_REQUEST_* env vars into the SIF. fail-loud at each step.
153
+ # REQUIRES a Trusted Publisher already configured on PyPI for this
154
+ # (project, owner, repo, workflow, environment) tuple — see the
155
+ # PREREQUISITE note at the top of this file.
156
+ - name: Publish to PyPI via manual OIDC trusted publishing in the SIF
157
+ run: bash .github/ci/exec-in-sif.sh publish-in-sif.sh 3.12
158
+
159
+ release:
160
+ needs: [build, publish]
161
+ runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
162
+ permissions:
163
+ contents: write
164
+ steps:
165
+ - uses: actions/checkout@v4
166
+ with:
167
+ ref: ${{ needs.build.outputs.version }}
168
+ - uses: actions/download-artifact@v4
169
+ with:
170
+ name: dist
171
+ path: dist
172
+ - name: Extract release notes from CHANGELOG.md
173
+ run: |
174
+ VERSION="${{ needs.build.outputs.version }}"
175
+ VERSION="${VERSION#v}"
176
+ awk -v v="$VERSION" '
177
+ $0 ~ "^## \\[" v "\\]" {p=1; next}
178
+ p && /^## \[/ {exit}
179
+ p
180
+ ' CHANGELOG.md > release-notes.md || true
181
+ if [ ! -s release-notes.md ]; then
182
+ echo "Release v${VERSION}. See CHANGELOG.md for details." > release-notes.md
183
+ fi
184
+ - name: Create GitHub Release
185
+ env:
186
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
187
+ run: |
188
+ gh release create "${{ needs.build.outputs.version }}" \
189
+ --title "${{ needs.build.outputs.version }}" \
190
+ --notes-file release-notes.md \
191
+ dist/*
@@ -0,0 +1,43 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ *.egg-info/
6
+ *.egg
7
+ dist/
8
+ build/
9
+ .eggs/
10
+ *.whl
11
+ .venv/
12
+ venv/
13
+ env/
14
+ .env
15
+ .tox/
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ *.swp
20
+ *.swo
21
+ *~
22
+ .DS_Store
23
+ .idea/
24
+ .vscode/
25
+
26
+ # direnv + git worktrees live in-tree but are never committed.
27
+ .envrc
28
+ .worktrees/
29
+
30
+ # scitex-dev local state: ignore everything under .scitex/ except the
31
+ # tracked dev/config.yaml and the CLI-audit noun/verb dict (both are audit
32
+ # inputs CI must see on a fresh checkout).
33
+ .scitex/*
34
+ !.scitex/dev/
35
+ .scitex/dev/*
36
+ !.scitex/dev/config.yaml
37
+ !.scitex/dev/cli-audit-dict.yaml
38
+
39
+ # Sphinx local build output (the in-wheel src/<pkg>/_sphinx_html bundle IS tracked)
40
+ docs/sphinx/_build/
41
+
42
+ # Operator's private brainstorm notes — never committed (see repo instructions).
43
+ GITIGNORED/