muninn-py 0.1.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 (75) hide show
  1. muninn_py-0.1.0/.claude/commands/whats-next.md +45 -0
  2. muninn_py-0.1.0/.claude/hooks/whats-next.sh +69 -0
  3. muninn_py-0.1.0/.claude/settings.json +17 -0
  4. muninn_py-0.1.0/.github/dependabot.yml +40 -0
  5. muninn_py-0.1.0/.github/workflows/ci.yml +96 -0
  6. muninn_py-0.1.0/.github/workflows/docs.yml +39 -0
  7. muninn_py-0.1.0/.github/workflows/integration.yml +173 -0
  8. muninn_py-0.1.0/.github/workflows/release.yml +131 -0
  9. muninn_py-0.1.0/.gitignore +64 -0
  10. muninn_py-0.1.0/CHANGELOG.md +49 -0
  11. muninn_py-0.1.0/CLAUDE.md +67 -0
  12. muninn_py-0.1.0/CODE_OF_CONDUCT.md +28 -0
  13. muninn_py-0.1.0/CONTRIBUTING.md +108 -0
  14. muninn_py-0.1.0/LICENSE +202 -0
  15. muninn_py-0.1.0/PKG-INFO +344 -0
  16. muninn_py-0.1.0/README.md +289 -0
  17. muninn_py-0.1.0/SECURITY.md +42 -0
  18. muninn_py-0.1.0/docs/RELEASING.md +76 -0
  19. muninn_py-0.1.0/docs/ROADMAP.md +130 -0
  20. muninn_py-0.1.0/docs/api/async_client.md +5 -0
  21. muninn_py-0.1.0/docs/api/client.md +5 -0
  22. muninn_py-0.1.0/docs/api/exceptions.md +13 -0
  23. muninn_py-0.1.0/docs/api/models.md +11 -0
  24. muninn_py-0.1.0/docs/api/notebook.md +19 -0
  25. muninn_py-0.1.0/docs/api/streaming.md +10 -0
  26. muninn_py-0.1.0/docs/changelog.md +5 -0
  27. muninn_py-0.1.0/docs/getting-started.md +136 -0
  28. muninn_py-0.1.0/docs/index.md +40 -0
  29. muninn_py-0.1.0/docs/notebooks.md +71 -0
  30. muninn_py-0.1.0/mkdocs.yml +63 -0
  31. muninn_py-0.1.0/notebooks/_build_drift_notebook.py +363 -0
  32. muninn_py-0.1.0/notebooks/alpha_backtest_demo.ipynb +246 -0
  33. muninn_py-0.1.0/notebooks/feature_drift_monitoring.ipynb +356 -0
  34. muninn_py-0.1.0/pyproject.toml +133 -0
  35. muninn_py-0.1.0/requirements-docs.txt +3 -0
  36. muninn_py-0.1.0/scripts/smoke.sh +186 -0
  37. muninn_py-0.1.0/setup.cfg +4 -0
  38. muninn_py-0.1.0/src/muninn/__init__.py +63 -0
  39. muninn_py-0.1.0/src/muninn/_cache.py +115 -0
  40. muninn_py-0.1.0/src/muninn/_retry.py +180 -0
  41. muninn_py-0.1.0/src/muninn/_transport.py +192 -0
  42. muninn_py-0.1.0/src/muninn/_version.py +7 -0
  43. muninn_py-0.1.0/src/muninn/async_client.py +329 -0
  44. muninn_py-0.1.0/src/muninn/cli.py +264 -0
  45. muninn_py-0.1.0/src/muninn/client.py +439 -0
  46. muninn_py-0.1.0/src/muninn/dashboard/__init__.py +60 -0
  47. muninn_py-0.1.0/src/muninn/dashboard/app.py +411 -0
  48. muninn_py-0.1.0/src/muninn/exceptions.py +53 -0
  49. muninn_py-0.1.0/src/muninn/models.py +116 -0
  50. muninn_py-0.1.0/src/muninn/notebook.py +219 -0
  51. muninn_py-0.1.0/src/muninn/pandas_client.py +184 -0
  52. muninn_py-0.1.0/src/muninn/py.typed +0 -0
  53. muninn_py-0.1.0/src/muninn/streaming.py +215 -0
  54. muninn_py-0.1.0/src/muninn_py.egg-info/PKG-INFO +344 -0
  55. muninn_py-0.1.0/src/muninn_py.egg-info/SOURCES.txt +73 -0
  56. muninn_py-0.1.0/src/muninn_py.egg-info/dependency_links.txt +1 -0
  57. muninn_py-0.1.0/src/muninn_py.egg-info/entry_points.txt +2 -0
  58. muninn_py-0.1.0/src/muninn_py.egg-info/requires.txt +30 -0
  59. muninn_py-0.1.0/src/muninn_py.egg-info/top_level.txt +1 -0
  60. muninn_py-0.1.0/tests/__init__.py +0 -0
  61. muninn_py-0.1.0/tests/bench_client.py +138 -0
  62. muninn_py-0.1.0/tests/conftest.py +13 -0
  63. muninn_py-0.1.0/tests/test_async_client.py +370 -0
  64. muninn_py-0.1.0/tests/test_cache.py +218 -0
  65. muninn_py-0.1.0/tests/test_cli.py +247 -0
  66. muninn_py-0.1.0/tests/test_client.py +346 -0
  67. muninn_py-0.1.0/tests/test_get_panel.py +163 -0
  68. muninn_py-0.1.0/tests/test_integration.py +721 -0
  69. muninn_py-0.1.0/tests/test_models.py +153 -0
  70. muninn_py-0.1.0/tests/test_notebook.py +164 -0
  71. muninn_py-0.1.0/tests/test_openapi_contract.py +214 -0
  72. muninn_py-0.1.0/tests/test_pandas_client.py +147 -0
  73. muninn_py-0.1.0/tests/test_retry.py +229 -0
  74. muninn_py-0.1.0/tests/test_streaming.py +126 -0
  75. muninn_py-0.1.0/tests/testdata/muninn_api_docs_v1.json +221 -0
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: Audit the current project state and surface what's next, what's missing, and what's at risk.
3
+ ---
4
+
5
+ Audit this project's current state and tell me three things, in this order:
6
+
7
+ 1. **What's next.** The next 3 actionable items, ranked by leverage. For each, name the file or component it touches and the single thing that would close it.
8
+ 2. **What's missing.** Anything tracked as deferred, partial, scaffolded, or TODO that should be revisited. Cross-reference these against the ROADMAP and any `🟡` markers.
9
+ 3. **What's at risk.** Items likely to silently rot — stale docs that contradict the code, untested code paths, ADRs the codebase has drifted from, dependencies overdue for a bump, exit criteria not yet verified by a test.
10
+
11
+ ## How to gather the picture
12
+
13
+ Read, in order:
14
+
15
+ - `docs/steering/ROADMAP.md` if present, otherwise `docs/ROADMAP.md`.
16
+ - The most recent `## [Unreleased]` block of `CHANGELOG.md`.
17
+ - The last 5 commits — `git log --oneline -5`.
18
+ - Anything in `TaskList` that's still `pending` or `in_progress`.
19
+ - `git status --short` for uncommitted work.
20
+
21
+ If this is a multi-repo session (e.g., both the server `muninn` and the SDK `muninn-py` are checked out), audit whichever working tree the current shell is in. Note the other one only if its state is directly load-bearing for an item.
22
+
23
+ ## How to report
24
+
25
+ Keep the whole reply under ~250 words. Format:
26
+
27
+ ```
28
+ ## What's next
29
+ 1. <action> — <file or component> — <closing condition>
30
+ 2. ...
31
+ 3. ...
32
+
33
+ ## What's missing
34
+ - <item> — <why it matters / what would close it>
35
+ - ...
36
+
37
+ ## What's at risk
38
+ - <item> — <how it might rot>
39
+ - ...
40
+
41
+ ## Recommended next move
42
+ <one sentence — usually pointing at the top "What's next" item>
43
+ ```
44
+
45
+ Don't dispatch agents. Don't run the test suite. Don't write or modify files. This is an audit, not work. If you can't tell the state of something from the docs + git log alone, say so explicitly.
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bash
2
+ # Stop hook — emits a one-line "next up" nudge after every Claude response.
3
+ #
4
+ # Reads ROADMAP.md (project conventions: docs/steering/ROADMAP.md or docs/ROADMAP.md),
5
+ # counts phase markers, and surfaces the next un-done phase. Silent when no roadmap
6
+ # is present so this hook is harmless in other repos that share the same global config.
7
+ #
8
+ # Designed to run in well under a second; no network, no Claude, just shell + grep.
9
+
10
+ set -euo pipefail
11
+
12
+ # Resolve repo root from $CLAUDE_PROJECT_DIR (set by Claude Code), or fall back to pwd.
13
+ ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
14
+
15
+ # Locate a roadmap file. First match wins.
16
+ ROADMAP=""
17
+ for candidate in \
18
+ "$ROOT/docs/steering/ROADMAP.md" \
19
+ "$ROOT/docs/ROADMAP.md" \
20
+ "$ROOT/ROADMAP.md"
21
+ do
22
+ if [ -f "$candidate" ]; then
23
+ ROADMAP="$candidate"
24
+ break
25
+ fi
26
+ done
27
+
28
+ # No roadmap — exit silently so the hook is invisible in unrelated repos.
29
+ [ -z "$ROADMAP" ] && exit 0
30
+
31
+ # Count phases by completion marker.
32
+ # A phase is "done" when its heading carries ✅ (complete), 🟢 (mostly complete),
33
+ # or _(Merged…)_ (rolled into another phase).
34
+ DONE_PATTERN='^## Phase .*(✅|🟢|_\(Merged)'
35
+ DONE=$(grep -cE "$DONE_PATTERN" "$ROADMAP" 2>/dev/null || true)
36
+ TOTAL=$(grep -cE '^## Phase ' "$ROADMAP" 2>/dev/null || true)
37
+
38
+ # Default counts to 0 if grep returned nothing.
39
+ DONE="${DONE:-0}"
40
+ TOTAL="${TOTAL:-0}"
41
+
42
+ # First "## Phase ..." line that isn't marked done. Strip the prefix + trailing
43
+ # section emoji/marker for a compact display.
44
+ NEXT_PHASE=$(
45
+ grep -E '^## Phase ' "$ROADMAP" 2>/dev/null \
46
+ | grep -vE '✅|🟢|_\(Merged' \
47
+ | head -n 1 \
48
+ | sed -E 's/^## Phase //; s/ —.*$//; s/ \(.*$//; s/ 🟡.*$//'
49
+ )
50
+
51
+ # Open work — anything with a 🟡 (scaffolded / in-progress) marker, useful as a
52
+ # soft heads-up.
53
+ OPEN=$(grep -cE '^- 🟡' "$ROADMAP" 2>/dev/null || true)
54
+ OPEN="${OPEN:-0}"
55
+
56
+ if [ -z "$NEXT_PHASE" ] && [ "$TOTAL" -gt 0 ] && [ "$DONE" -eq "$TOTAL" ]; then
57
+ printf '→ Roadmap fully ✅ (%s/%s). Try /whats-next for follow-ups or post-Phase ideas.\n' \
58
+ "$DONE" "$TOTAL"
59
+ elif [ -n "$NEXT_PHASE" ]; then
60
+ if [ "$OPEN" -gt 0 ]; then
61
+ printf '→ Next: Phase %s · %s/%s done · %s open item(s) tagged 🟡 · /whats-next for detail.\n' \
62
+ "$NEXT_PHASE" "$DONE" "$TOTAL" "$OPEN"
63
+ else
64
+ printf '→ Next: Phase %s · %s/%s done · /whats-next for detail.\n' \
65
+ "$NEXT_PHASE" "$DONE" "$TOTAL"
66
+ fi
67
+ fi
68
+
69
+ exit 0
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://www.schemastore.org/claude-code-settings.json",
3
+ "hooks": {
4
+ "Stop": [
5
+ {
6
+ "matcher": "",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": ".claude/hooks/whats-next.sh",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,40 @@
1
+ version: 2
2
+
3
+ # Dependabot configuration for muninn-py.
4
+ # - Watches pip dependencies declared in pyproject.toml.
5
+ # - Watches the GitHub Actions used by .github/workflows/*.
6
+ # - Weekly cadence; small bumps grouped to reduce noise.
7
+
8
+ updates:
9
+ - package-ecosystem: "pip"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
13
+ day: "monday"
14
+ open-pull-requests-limit: 10
15
+ groups:
16
+ runtime:
17
+ applies-to: version-updates
18
+ patterns:
19
+ - "httpx"
20
+ - "polars"
21
+ - "pandas"
22
+ - "pydantic"
23
+ dev:
24
+ applies-to: version-updates
25
+ patterns:
26
+ - "pytest*"
27
+ - "respx"
28
+ - "ruff"
29
+ - "mypy"
30
+ labels:
31
+ - "dependencies"
32
+
33
+ - package-ecosystem: "github-actions"
34
+ directory: "/"
35
+ schedule:
36
+ interval: "weekly"
37
+ day: "monday"
38
+ labels:
39
+ - "dependencies"
40
+ - "github-actions"
@@ -0,0 +1,96 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ test:
16
+ name: test (py${{ matrix.python }})
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 15
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python: ["3.10", "3.11", "3.12"]
23
+
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+
27
+ - name: Set up Python ${{ matrix.python }}
28
+ uses: actions/setup-python@v6
29
+ with:
30
+ python-version: ${{ matrix.python }}
31
+ cache: pip
32
+
33
+ - name: Install
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ pip install -e ".[dev]"
37
+
38
+ - name: Lint
39
+ run: ruff check .
40
+
41
+ - name: Type-check
42
+ run: mypy src/muninn
43
+
44
+ - name: Tests
45
+ run: pytest -ra
46
+
47
+ benchmark:
48
+ name: benchmarks (py3.12)
49
+ runs-on: ubuntu-latest
50
+ timeout-minutes: 15
51
+
52
+ steps:
53
+ - uses: actions/checkout@v6
54
+
55
+ - name: Set up Python 3.12
56
+ uses: actions/setup-python@v6
57
+ with:
58
+ python-version: "3.12"
59
+ cache: pip
60
+
61
+ - name: Install
62
+ run: |
63
+ python -m pip install --upgrade pip
64
+ pip install -e ".[dev]"
65
+
66
+ - name: Run benchmarks
67
+ # --benchmark-only: run only benchmark tests (skip regular tests)
68
+ # --benchmark-json: save results for artifact upload
69
+ # When .benchmarks/baseline.json exists (committed), --benchmark-compare
70
+ # enforces the 25% regression gate. Until a baseline is committed, this
71
+ # step runs in observation mode and saves the results as an artifact.
72
+ run: |
73
+ if [ -f .benchmarks/baseline.json ]; then
74
+ pytest tests/bench_client.py \
75
+ --benchmark-only \
76
+ --benchmark-compare=.benchmarks/baseline.json \
77
+ --benchmark-fail-max-delta-mean=0.25 \
78
+ --benchmark-json=benchmark-results.json \
79
+ -v
80
+ else
81
+ pytest tests/bench_client.py \
82
+ --benchmark-only \
83
+ --benchmark-json=benchmark-results.json \
84
+ -v
85
+ echo "No baseline found. To enable the regression gate, run:"
86
+ echo " pytest tests/bench_client.py --benchmark-save=baseline --benchmark-only"
87
+ echo "then commit .benchmarks/baseline.json"
88
+ fi
89
+
90
+ - name: Upload benchmark results
91
+ uses: actions/upload-artifact@v7
92
+ if: always()
93
+ with:
94
+ name: benchmark-results-py3.12
95
+ path: benchmark-results.json
96
+ retention-days: 30
@@ -0,0 +1,39 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - "mkdocs.yml"
9
+ - "requirements-docs.txt"
10
+ - "src/muninn/**"
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: write
15
+
16
+ jobs:
17
+ deploy:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+
21
+ steps:
22
+ - uses: actions/checkout@v6
23
+ with:
24
+ fetch-depth: 0
25
+
26
+ - uses: actions/setup-python@v6
27
+ with:
28
+ python-version: "3.12"
29
+ cache: pip
30
+ cache-dependency-path: requirements-docs.txt
31
+
32
+ - name: Install docs dependencies
33
+ run: pip install -r requirements-docs.txt
34
+
35
+ - name: Install muninn-py (for mkdocstrings introspection)
36
+ run: pip install -e .
37
+
38
+ - name: Deploy to GitHub Pages
39
+ run: mkdocs gh-deploy --force
@@ -0,0 +1,173 @@
1
+ name: Integration
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: integration-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ integration-tests:
16
+ name: integration tests (py3.12)
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 20
19
+
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+
23
+ - name: Check out Muninn server
24
+ uses: actions/checkout@v6
25
+ with:
26
+ repository: lgreene03/muninn
27
+ path: muninn
28
+
29
+ - name: Set up Python 3.12
30
+ uses: actions/setup-python@v6
31
+ with:
32
+ python-version: "3.12"
33
+ cache: pip
34
+
35
+ - name: Install SDK with test extras
36
+ run: |
37
+ python -m pip install --upgrade pip
38
+ pip install -e ".[dev]"
39
+
40
+ - name: Run integration tests
41
+ run: pytest -m integration -ra -v
42
+ env:
43
+ MUNINN_SERVER_DIR: ${{ github.workspace }}/muninn
44
+
45
+ notebook-execution:
46
+ name: notebook smoke test (py3.12)
47
+ runs-on: ubuntu-latest
48
+ timeout-minutes: 25
49
+
50
+ steps:
51
+ - uses: actions/checkout@v6
52
+
53
+ - name: Check out Muninn server
54
+ uses: actions/checkout@v6
55
+ with:
56
+ repository: lgreene03/muninn
57
+ path: muninn-server
58
+
59
+ - name: Set up Python 3.12
60
+ uses: actions/setup-python@v6
61
+ with:
62
+ python-version: "3.12"
63
+ cache: pip
64
+
65
+ - name: Install SDK with notebook extras
66
+ run: |
67
+ python -m pip install --upgrade pip
68
+ pip install -e ".[notebooks,dev]"
69
+ pip install nbconvert ipykernel
70
+
71
+ - name: Register Jupyter kernel
72
+ run: python -m ipykernel install --user --name muninn-py
73
+
74
+ - name: Build Muninn server image
75
+ working-directory: muninn-server
76
+ run: docker build -t muninn:ci .
77
+
78
+ - name: Start infrastructure
79
+ working-directory: muninn-server
80
+ run: docker compose up -d
81
+
82
+ - name: Wait for infrastructure health
83
+ working-directory: muninn-server
84
+ run: |
85
+ echo "Waiting for PostgreSQL..."
86
+ until docker compose exec -T postgres pg_isready -U muninn; do sleep 2; done
87
+ echo "Waiting for Redpanda..."
88
+ until docker compose exec -T redpanda rpk cluster health; do sleep 2; done
89
+ echo "Waiting for MinIO..."
90
+ until docker compose exec -T minio mc ready local; do sleep 2; done
91
+
92
+ - name: Start Muninn server
93
+ working-directory: muninn-server
94
+ run: |
95
+ docker run -d --name muninn-server \
96
+ --network "$(docker compose ps -q postgres | xargs docker inspect -f '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' | head -1)" \
97
+ -e SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/muninn \
98
+ -e SPRING_DATASOURCE_USERNAME=muninn \
99
+ -e SPRING_DATASOURCE_PASSWORD=muninn \
100
+ -e SPRING_KAFKA_BOOTSTRAP_SERVERS=redpanda:9092 \
101
+ -e MUNINN_STORAGE_S3_ENDPOINT=http://minio:9000 \
102
+ -e MUNINN_STORAGE_S3_ACCESS_KEY=minioadmin \
103
+ -e MUNINN_STORAGE_S3_SECRET_KEY=minioadmin \
104
+ -e MUNINN_INGESTION_BINANCE_ENABLED=false \
105
+ -p 8080:8080 \
106
+ muninn:ci
107
+
108
+ - name: Wait for Muninn API
109
+ run: |
110
+ echo "Waiting for Muninn API..."
111
+ for i in $(seq 1 60); do
112
+ if curl -sf http://localhost:8080/actuator/health > /dev/null 2>&1; then
113
+ echo "Muninn API is up"
114
+ break
115
+ fi
116
+ if [ $i -eq 60 ]; then
117
+ echo "Muninn API failed to start"
118
+ docker logs muninn-server
119
+ exit 1
120
+ fi
121
+ sleep 3
122
+ done
123
+
124
+ - name: Seed synthetic data via smoke script
125
+ working-directory: muninn-server
126
+ run: |
127
+ if [ -f scripts/smoke.sh ]; then
128
+ bash scripts/smoke.sh || echo "Smoke script exited with $? (non-fatal for notebook test)"
129
+ else
130
+ echo "No smoke script found — notebooks may run with empty data"
131
+ fi
132
+
133
+ - name: Execute alpha backtest notebook
134
+ run: |
135
+ jupyter nbconvert \
136
+ --to notebook \
137
+ --execute \
138
+ --ExecutePreprocessor.timeout=120 \
139
+ --ExecutePreprocessor.kernel_name=muninn-py \
140
+ --output-dir=/tmp/notebook-output \
141
+ notebooks/alpha_backtest_demo.ipynb || {
142
+ echo "::warning::alpha_backtest_demo.ipynb failed — server may lack sufficient data"
143
+ exit 0
144
+ }
145
+
146
+ - name: Execute feature drift monitoring notebook
147
+ run: |
148
+ jupyter nbconvert \
149
+ --to notebook \
150
+ --execute \
151
+ --ExecutePreprocessor.timeout=120 \
152
+ --ExecutePreprocessor.kernel_name=muninn-py \
153
+ --output-dir=/tmp/notebook-output \
154
+ notebooks/feature_drift_monitoring.ipynb || {
155
+ echo "::warning::feature_drift_monitoring.ipynb failed — server may lack sufficient data"
156
+ exit 0
157
+ }
158
+
159
+ - name: Upload executed notebooks
160
+ uses: actions/upload-artifact@v7
161
+ if: always()
162
+ with:
163
+ name: executed-notebooks
164
+ path: /tmp/notebook-output/
165
+ retention-days: 14
166
+
167
+ - name: Cleanup
168
+ if: always()
169
+ working-directory: muninn-server
170
+ run: |
171
+ docker stop muninn-server || true
172
+ docker rm muninn-server || true
173
+ docker compose down -v || true
@@ -0,0 +1,131 @@
1
+ name: Release
2
+
3
+ # Tagged release flow:
4
+ # 1. Bump the version in pyproject.toml + src/muninn/_version.py + CHANGELOG.
5
+ # 2. Commit, tag (vX.Y.Z), and push the tag.
6
+ # 3. This workflow builds the sdist + wheel and publishes to PyPI via
7
+ # Trusted Publishing (OIDC) — no API tokens stored as secrets.
8
+ #
9
+ # One-time setup before the first publish:
10
+ # - Reserve the name `muninn-py` on PyPI (https://pypi.org/manage/account/publishing/).
11
+ # - Add a "Trusted publisher" pointing at this workflow:
12
+ # PyPI Project Name : muninn-py
13
+ # Owner : lgreene03
14
+ # Repository name : muninn-py
15
+ # Workflow name : release.yml
16
+ # Environment name : pypi
17
+ # - The pre-release run against TestPyPI uses the `testpypi` environment;
18
+ # register the equivalent trusted publisher at https://test.pypi.org.
19
+ #
20
+ # Manual dispatch is supported for republishing the latest build to TestPyPI
21
+ # without cutting a new tag.
22
+
23
+ on:
24
+ push:
25
+ tags:
26
+ - "v*.*.*"
27
+ workflow_dispatch:
28
+ inputs:
29
+ target:
30
+ description: "Where to upload"
31
+ required: true
32
+ default: "testpypi"
33
+ type: choice
34
+ options:
35
+ - testpypi
36
+ - pypi
37
+
38
+ permissions:
39
+ contents: read
40
+
41
+ concurrency:
42
+ group: release-${{ github.ref }}
43
+ cancel-in-progress: false
44
+
45
+ jobs:
46
+ build:
47
+ name: build distributions
48
+ runs-on: ubuntu-latest
49
+ timeout-minutes: 10
50
+ steps:
51
+ - uses: actions/checkout@v6
52
+
53
+ - name: Set up Python
54
+ uses: actions/setup-python@v6
55
+ with:
56
+ python-version: "3.12"
57
+
58
+ - name: Install build tooling
59
+ run: |
60
+ python -m pip install --upgrade pip
61
+ pip install build twine
62
+
63
+ - name: Build sdist + wheel
64
+ run: python -m build
65
+
66
+ - name: Check artifact metadata
67
+ run: twine check dist/*
68
+
69
+ - name: Verify version matches tag (tag-triggered runs only)
70
+ if: startsWith(github.ref, 'refs/tags/v')
71
+ run: |
72
+ tag_version="${GITHUB_REF_NAME#v}"
73
+ pkg_version=$(python -c "import tomllib; print(tomllib.loads(open('pyproject.toml','rb').read().decode())['project']['version'])")
74
+ if [ "$tag_version" != "$pkg_version" ]; then
75
+ echo "::error::Tag $GITHUB_REF_NAME does not match pyproject.toml version $pkg_version"
76
+ exit 1
77
+ fi
78
+ echo "Tag $GITHUB_REF_NAME matches pyproject.toml version $pkg_version"
79
+
80
+ - name: Upload built artifacts
81
+ uses: actions/upload-artifact@v7
82
+ with:
83
+ name: dist
84
+ path: dist/
85
+ retention-days: 14
86
+
87
+ publish-testpypi:
88
+ name: publish to TestPyPI
89
+ runs-on: ubuntu-latest
90
+ needs: build
91
+ if: |
92
+ github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
93
+ environment:
94
+ name: testpypi
95
+ url: https://test.pypi.org/project/muninn-py/
96
+ permissions:
97
+ id-token: write
98
+ steps:
99
+ - name: Download distributions
100
+ uses: actions/download-artifact@v8
101
+ with:
102
+ name: dist
103
+ path: dist/
104
+
105
+ - name: Publish to TestPyPI
106
+ uses: pypa/gh-action-pypi-publish@release/v1
107
+ with:
108
+ repository-url: https://test.pypi.org/legacy/
109
+ skip-existing: true
110
+
111
+ publish-pypi:
112
+ name: publish to PyPI
113
+ runs-on: ubuntu-latest
114
+ needs: build
115
+ if: |
116
+ startsWith(github.ref, 'refs/tags/v')
117
+ || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
118
+ environment:
119
+ name: pypi
120
+ url: https://pypi.org/project/muninn-py/
121
+ permissions:
122
+ id-token: write
123
+ steps:
124
+ - name: Download distributions
125
+ uses: actions/download-artifact@v8
126
+ with:
127
+ name: dist
128
+ path: dist/
129
+
130
+ - name: Publish to PyPI
131
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,64 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ *.egg
21
+ MANIFEST
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ env/
27
+ ENV/
28
+
29
+ # Test / type / lint
30
+ .pytest_cache/
31
+ .mypy_cache/
32
+ .ruff_cache/
33
+ .coverage
34
+ .coverage.*
35
+ htmlcov/
36
+ .tox/
37
+ .nox/
38
+
39
+ # Jupyter
40
+ .ipynb_checkpoints/
41
+ *.ipynb-checkpoint
42
+
43
+ # IDE
44
+ .idea/
45
+ .vscode/
46
+ *.swp
47
+ *.swo
48
+
49
+ # OS
50
+ .DS_Store
51
+ Thumbs.db
52
+
53
+ # MkDocs
54
+ site/
55
+
56
+ # Benchmarks
57
+ .benchmarks/
58
+
59
+ # Local data
60
+ data/
61
+ *.parquet
62
+ *.feather
63
+ .env
64
+ .env.local