stemtrace 0.3.1__tar.gz → 0.3.3__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.
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.bumpversion.toml +1 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/workflows/ci.yml +1 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/workflows/e2e.yml +4 -4
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/workflows/release.yml +10 -6
- {stemtrace-0.3.1 → stemtrace-0.3.3}/CHANGELOG.md +14 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/PKG-INFO +6 -2
- {stemtrace-0.3.1 → stemtrace-0.3.3}/README.md +5 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/pyproject.toml +1 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/__init__.py +13 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/config.py +6 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/redis.py +29 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/routes.py +57 -4
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/extension.py +6 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/router.py +2 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/package-lock.json +313 -96
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/package.json +5 -5
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_init.py +2 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_routes.py +141 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_transports.py +38 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/uv.lock +1 -1
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.dockerignore +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/dependabot.yml +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.gitignore +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.pre-commit-config.yaml +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/.python-version +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/CONTRIBUTING.md +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/Dockerfile +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/Dockerfile.e2e +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/LICENSE +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/Makefile +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/build_ui.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.e2e.rabbitmq.yml +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.e2e.yml +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.rabbitmq.yml +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.yml +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docs/screenshots/task_details.png +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docs/screenshots/unregistered.png +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/docs/screenshots/workflow.png +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/celery_app.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/fastapi_integration.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/with_auth.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/with_login.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/progress.md +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/scripts/wait_for_http.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/events.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/exceptions.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/graph.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/ports.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/bootsteps.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/scrubbing.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/signals.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/memory.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/rabbitmq.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/py.typed +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/__main__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/schemas.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/websocket.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/consumer.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/auth.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/form_auth.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/login_routes.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/store.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/biome.json +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/index.html +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/api/client.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/static.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/templates/login.html +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/websocket.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/conftest.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/conftest.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/tasks.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/test_api_e2e.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/test_workers_e2e.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_embedded_static.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_fastapi_integration.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_rabbitmq_transport.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_redis_transport.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_static_ui.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_workers_refresh.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/__init__.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_auth.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_bootsteps.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_config.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_consumer.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_events.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_examples.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_exceptions.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_form_auth.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_form_login.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_graph.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_scrubbing.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_signals.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_store.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_ui_static_prefix.py +0 -0
- {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_websocket.py +0 -0
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
- uses: actions/checkout@v6
|
|
25
25
|
|
|
26
26
|
- name: Set up Docker Buildx
|
|
27
|
-
uses: docker/setup-buildx-action@
|
|
27
|
+
uses: docker/setup-buildx-action@v4
|
|
28
28
|
|
|
29
29
|
- name: Select compose file and Celery env
|
|
30
30
|
run: |
|
|
@@ -91,7 +91,7 @@ jobs:
|
|
|
91
91
|
- uses: actions/checkout@v6
|
|
92
92
|
|
|
93
93
|
- name: Set up Docker Buildx
|
|
94
|
-
uses: docker/setup-buildx-action@
|
|
94
|
+
uses: docker/setup-buildx-action@v4
|
|
95
95
|
|
|
96
96
|
- name: Select compose file and Celery env
|
|
97
97
|
run: |
|
|
@@ -167,7 +167,7 @@ jobs:
|
|
|
167
167
|
PLAYWRIGHT_BASE_URL: http://localhost:8000
|
|
168
168
|
|
|
169
169
|
- name: Upload Playwright report
|
|
170
|
-
uses: actions/upload-artifact@
|
|
170
|
+
uses: actions/upload-artifact@v7
|
|
171
171
|
if: always()
|
|
172
172
|
with:
|
|
173
173
|
name: playwright-report-${{ matrix.broker }}
|
|
@@ -175,7 +175,7 @@ jobs:
|
|
|
175
175
|
retention-days: 7
|
|
176
176
|
|
|
177
177
|
- name: Upload test results
|
|
178
|
-
uses: actions/upload-artifact@
|
|
178
|
+
uses: actions/upload-artifact@v7
|
|
179
179
|
if: failure()
|
|
180
180
|
with:
|
|
181
181
|
name: playwright-test-results-${{ matrix.broker }}
|
|
@@ -74,7 +74,7 @@ jobs:
|
|
|
74
74
|
run: twine check dist/*
|
|
75
75
|
|
|
76
76
|
- name: Upload dist artifacts
|
|
77
|
-
uses: actions/upload-artifact@
|
|
77
|
+
uses: actions/upload-artifact@v7
|
|
78
78
|
with:
|
|
79
79
|
name: dist
|
|
80
80
|
path: dist/
|
|
@@ -87,7 +87,7 @@ jobs:
|
|
|
87
87
|
id-token: write
|
|
88
88
|
steps:
|
|
89
89
|
- name: Download dist artifacts
|
|
90
|
-
uses: actions/download-artifact@
|
|
90
|
+
uses: actions/download-artifact@v8
|
|
91
91
|
with:
|
|
92
92
|
name: dist
|
|
93
93
|
path: dist/
|
|
@@ -104,8 +104,11 @@ jobs:
|
|
|
104
104
|
steps:
|
|
105
105
|
- uses: actions/checkout@v6
|
|
106
106
|
|
|
107
|
+
- name: Set up QEMU
|
|
108
|
+
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
|
109
|
+
|
|
107
110
|
- name: Set up Docker Buildx
|
|
108
|
-
uses: docker/setup-buildx-action@
|
|
111
|
+
uses: docker/setup-buildx-action@v4
|
|
109
112
|
|
|
110
113
|
- name: Log in to GitHub Container Registry
|
|
111
114
|
uses: docker/login-action@v3
|
|
@@ -116,7 +119,7 @@ jobs:
|
|
|
116
119
|
|
|
117
120
|
- name: Extract metadata
|
|
118
121
|
id: meta
|
|
119
|
-
uses: docker/metadata-action@
|
|
122
|
+
uses: docker/metadata-action@v6
|
|
120
123
|
with:
|
|
121
124
|
images: ghcr.io/${{ github.repository }}
|
|
122
125
|
tags: |
|
|
@@ -124,10 +127,11 @@ jobs:
|
|
|
124
127
|
type=raw,value=latest
|
|
125
128
|
|
|
126
129
|
- name: Build and push Docker image
|
|
127
|
-
uses: docker/build-push-action@
|
|
130
|
+
uses: docker/build-push-action@v7
|
|
128
131
|
with:
|
|
129
132
|
context: .
|
|
130
133
|
push: true
|
|
134
|
+
platforms: linux/amd64,linux/arm64
|
|
131
135
|
tags: ${{ steps.meta.outputs.tags }}
|
|
132
136
|
labels: ${{ steps.meta.outputs.labels }}
|
|
133
137
|
cache-from: type=gha
|
|
@@ -142,7 +146,7 @@ jobs:
|
|
|
142
146
|
- uses: actions/checkout@v6
|
|
143
147
|
|
|
144
148
|
- name: Download dist artifacts
|
|
145
|
-
uses: actions/download-artifact@
|
|
149
|
+
uses: actions/download-artifact@v8
|
|
146
150
|
with:
|
|
147
151
|
name: dist
|
|
148
152
|
path: dist/
|
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.3] - 2026-03-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Node alias from arguments: derive graph node display name from task args/kwargs via `node_alias_from_arguments` config option (thanks @ohadmata for the idea — #46, #48)
|
|
14
|
+
- Docker: multi-architecture image builds (amd64 + arm64) (thanks @ohadmata — #51)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Graph: order task nodes before synthetic container nodes (GROUP/CHORD) for correct rendering (thanks @codyburrito — #52)
|
|
18
|
+
|
|
19
|
+
## [0.3.2] - 2026-02-28
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Redis transport: normalize `ssl_cert_reqs` URL parameter from `CERT_REQUIRED`/`CERT_NONE`/`CERT_OPTIONAL` to lowercase values expected by redis-py
|
|
23
|
+
|
|
10
24
|
## [0.3.1] - 2026-01-04
|
|
11
25
|
|
|
12
26
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stemtrace
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A lightweight Celery task flow visualizer
|
|
5
5
|
Project-URL: Homepage, https://github.com/iansokolskyi/stemtrace
|
|
6
6
|
Project-URL: Documentation, https://github.com/iansokolskyi/stemtrace#readme
|
|
@@ -56,7 +56,7 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
|
|
57
57
|
**Zero-infrastructure Celery task flow visualizer**
|
|
58
58
|
|
|
59
|
-
[](https://pypi.org/project/stemtrace)
|
|
60
60
|
[](https://pypi.org/project/stemtrace/)
|
|
61
61
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
62
62
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -250,6 +250,10 @@ stemtrace.init_worker(
|
|
|
250
250
|
scrub_sensitive_data=True, # Scrub passwords, API keys, etc.
|
|
251
251
|
additional_sensitive_keys=frozenset({"my_secret"}), # Add custom keys
|
|
252
252
|
safe_keys=frozenset({"public_key"}), # Never scrub these keys
|
|
253
|
+
|
|
254
|
+
# UI display
|
|
255
|
+
node_alias_from_arguments="operator_type", # Use kwargs["operator_type"] as node name
|
|
256
|
+
# node_alias_from_arguments="0", # Or use args[0] (digit string = positional index)
|
|
253
257
|
)
|
|
254
258
|
|
|
255
259
|
# Introspection (after init)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Zero-infrastructure Celery task flow visualizer**
|
|
4
4
|
|
|
5
|
-
[](https://pypi.org/project/stemtrace)
|
|
6
6
|
[](https://pypi.org/project/stemtrace/)
|
|
7
7
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
8
8
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -196,6 +196,10 @@ stemtrace.init_worker(
|
|
|
196
196
|
scrub_sensitive_data=True, # Scrub passwords, API keys, etc.
|
|
197
197
|
additional_sensitive_keys=frozenset({"my_secret"}), # Add custom keys
|
|
198
198
|
safe_keys=frozenset({"public_key"}), # Never scrub these keys
|
|
199
|
+
|
|
200
|
+
# UI display
|
|
201
|
+
node_alias_from_arguments="operator_type", # Use kwargs["operator_type"] as node name
|
|
202
|
+
# node_alias_from_arguments="0", # Or use args[0] (digit string = positional index)
|
|
199
203
|
)
|
|
200
204
|
|
|
201
205
|
# Introspection (after init)
|
|
@@ -43,7 +43,7 @@ if TYPE_CHECKING:
|
|
|
43
43
|
from celery import Celery
|
|
44
44
|
from fastapi import FastAPI
|
|
45
45
|
|
|
46
|
-
__version__ = "0.3.
|
|
46
|
+
__version__ = "0.3.3"
|
|
47
47
|
__all__ = [
|
|
48
48
|
"ConfigurationError",
|
|
49
49
|
"StemtraceConfig",
|
|
@@ -92,6 +92,8 @@ def init_worker(
|
|
|
92
92
|
scrub_sensitive_data: bool = True,
|
|
93
93
|
additional_sensitive_keys: frozenset[str] | None = None,
|
|
94
94
|
safe_keys: frozenset[str] | None = None,
|
|
95
|
+
max_data_size: int = 10240,
|
|
96
|
+
node_alias_from_arguments: str | None = None,
|
|
95
97
|
) -> None:
|
|
96
98
|
"""Initialize stemtrace for Celery worker instrumentation.
|
|
97
99
|
|
|
@@ -105,6 +107,10 @@ def init_worker(
|
|
|
105
107
|
scrub_sensitive_data: Whether to scrub sensitive keys (default: True).
|
|
106
108
|
additional_sensitive_keys: Extra keys to treat as sensitive.
|
|
107
109
|
safe_keys: Keys to never scrub (overrides sensitive).
|
|
110
|
+
max_data_size: Maximum size in bytes for serialized data (default: 10KB).
|
|
111
|
+
node_alias_from_arguments: Derive node display name from task arguments.
|
|
112
|
+
Pass a digit string to use args[index], a string key for kwargs[key],
|
|
113
|
+
or None to use the task name (default).
|
|
108
114
|
|
|
109
115
|
Raises:
|
|
110
116
|
ConfigurationError: If no broker URL can be determined.
|
|
@@ -127,6 +133,8 @@ def init_worker(
|
|
|
127
133
|
scrub_sensitive_data=scrub_sensitive_data,
|
|
128
134
|
additional_sensitive_keys=additional_sensitive_keys or frozenset(),
|
|
129
135
|
safe_keys=safe_keys or frozenset(),
|
|
136
|
+
max_data_size=max_data_size,
|
|
137
|
+
node_alias_from_arguments=node_alias_from_arguments,
|
|
130
138
|
)
|
|
131
139
|
set_config(config)
|
|
132
140
|
|
|
@@ -150,6 +158,7 @@ def init_app(
|
|
|
150
158
|
login_password: str | None = None,
|
|
151
159
|
login_secret: str | None = None,
|
|
152
160
|
login_ttl_seconds: int = 86400,
|
|
161
|
+
node_alias_from_arguments: str | None = None,
|
|
153
162
|
) -> StemtraceExtension:
|
|
154
163
|
"""Initialize stemtrace as a FastAPI extension.
|
|
155
164
|
|
|
@@ -171,6 +180,8 @@ def init_app(
|
|
|
171
180
|
STEMTRACE_LOGIN_SECRET env var. If still missing, a random secret is
|
|
172
181
|
generated (sessions invalidated on app restart).
|
|
173
182
|
login_ttl_seconds: Session TTL in seconds (default: 24 hours).
|
|
183
|
+
node_alias_from_arguments: Derive graph node display name from task arguments.
|
|
184
|
+
Digit string for args[index], string for kwargs[key], or None (default).
|
|
174
185
|
|
|
175
186
|
Returns:
|
|
176
187
|
The initialized StemtraceExtension instance.
|
|
@@ -227,6 +238,7 @@ def init_app(
|
|
|
227
238
|
max_nodes=max_nodes,
|
|
228
239
|
auth_dependency=auth_dependency,
|
|
229
240
|
form_auth_config=form_auth_config,
|
|
241
|
+
node_alias_from_arguments=node_alias_from_arguments,
|
|
230
242
|
)
|
|
231
243
|
extension.init_app(fastapi_app, prefix=prefix)
|
|
232
244
|
|
|
@@ -16,6 +16,9 @@ class StemtraceConfig(BaseModel):
|
|
|
16
16
|
scrub_sensitive_data: Whether to scrub sensitive keys.
|
|
17
17
|
additional_sensitive_keys: Extra keys to treat as sensitive.
|
|
18
18
|
safe_keys: Keys to never scrub (overrides sensitive).
|
|
19
|
+
node_alias_from_arguments: Derive graph node display name from task
|
|
20
|
+
arguments. Pass a string key to use kwargs[key], a digit string
|
|
21
|
+
to use args[index], or None to use the task name (default).
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
model_config = ConfigDict(frozen=True)
|
|
@@ -34,6 +37,9 @@ class StemtraceConfig(BaseModel):
|
|
|
34
37
|
additional_sensitive_keys: frozenset[str] = Field(default_factory=frozenset)
|
|
35
38
|
safe_keys: frozenset[str] = Field(default_factory=frozenset)
|
|
36
39
|
|
|
40
|
+
# UI display options
|
|
41
|
+
node_alias_from_arguments: str | None = None
|
|
42
|
+
|
|
37
43
|
|
|
38
44
|
_config: StemtraceConfig | None = None
|
|
39
45
|
|
|
@@ -21,6 +21,34 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
# Type alias for all events that can be consumed from the stream
|
|
22
22
|
StreamEvent = TaskEvent | WorkerEvent
|
|
23
23
|
|
|
24
|
+
# Mapping from CERT_* constants (used in URLs) to lowercase values expected by redis-py
|
|
25
|
+
_SSL_CERT_REQS_MAP = {
|
|
26
|
+
"CERT_NONE": "none",
|
|
27
|
+
"CERT_OPTIONAL": "optional",
|
|
28
|
+
"CERT_REQUIRED": "required",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _normalize_redis_ssl_params(url: str) -> str:
|
|
33
|
+
"""Normalize ssl_cert_reqs in a Redis URL from CERT_* to lowercase.
|
|
34
|
+
|
|
35
|
+
redis-py expects lowercase values (none/optional/required) but URLs
|
|
36
|
+
commonly use the Python ssl module constants (CERT_NONE/CERT_REQUIRED).
|
|
37
|
+
"""
|
|
38
|
+
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
|
39
|
+
|
|
40
|
+
parsed = urlparse(url)
|
|
41
|
+
if not parsed.query:
|
|
42
|
+
return url
|
|
43
|
+
params = parse_qs(parsed.query)
|
|
44
|
+
if "ssl_cert_reqs" not in params:
|
|
45
|
+
return url
|
|
46
|
+
raw = params["ssl_cert_reqs"][0]
|
|
47
|
+
normalized = _SSL_CERT_REQS_MAP.get(raw, raw)
|
|
48
|
+
params["ssl_cert_reqs"] = [normalized]
|
|
49
|
+
new_query = urlencode(params, doseq=True)
|
|
50
|
+
return urlunparse(parsed._replace(query=new_query))
|
|
51
|
+
|
|
24
52
|
|
|
25
53
|
class RedisTransport:
|
|
26
54
|
"""Redis Streams-based event transport (XADD/XREAD)."""
|
|
@@ -129,6 +157,7 @@ class RedisTransport:
|
|
|
129
157
|
"""Create transport from Redis URL."""
|
|
130
158
|
from redis import Redis as RedisClient
|
|
131
159
|
|
|
160
|
+
url = _normalize_redis_ssl_params(url)
|
|
132
161
|
return cls(
|
|
133
162
|
client=RedisClient.from_url(url, decode_responses=False),
|
|
134
163
|
prefix=prefix,
|
|
@@ -123,9 +123,40 @@ def _node_to_response(node: TaskNode) -> TaskNodeResponse:
|
|
|
123
123
|
)
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def _resolve_node_alias(node: TaskNode, key: str | None) -> str:
|
|
127
|
+
"""Resolve display name for a graph node from task arguments.
|
|
128
|
+
|
|
129
|
+
When a key is configured, attempts to extract a value from the first
|
|
130
|
+
event's args (if key is a digit string) or kwargs (if key is a regular
|
|
131
|
+
string). Falls back to the node's registered task name.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
node: Task node containing events with args/kwargs.
|
|
135
|
+
key: Digit string for args[index], string for kwargs[key],
|
|
136
|
+
or None to use the task name.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Resolved display name for the node.
|
|
140
|
+
"""
|
|
141
|
+
if key is None or not node.events:
|
|
142
|
+
return node.name
|
|
143
|
+
|
|
144
|
+
event = node.events[0]
|
|
145
|
+
|
|
146
|
+
if key.isdigit():
|
|
147
|
+
index = int(key)
|
|
148
|
+
if isinstance(event.args, list) and index < len(event.args):
|
|
149
|
+
return str(event.args[index])
|
|
150
|
+
elif isinstance(event.kwargs, dict) and key in event.kwargs:
|
|
151
|
+
return str(event.kwargs[key])
|
|
152
|
+
|
|
153
|
+
return node.name
|
|
154
|
+
|
|
155
|
+
|
|
126
156
|
def _node_to_graph_response(
|
|
127
157
|
node: TaskNode,
|
|
128
158
|
all_nodes: dict[str, TaskNode] | None = None,
|
|
159
|
+
node_alias_key: str | None = None,
|
|
129
160
|
) -> GraphNodeResponse:
|
|
130
161
|
"""Convert TaskNode to graph response model.
|
|
131
162
|
|
|
@@ -134,6 +165,7 @@ def _node_to_graph_response(
|
|
|
134
165
|
Args:
|
|
135
166
|
node: Task node from graph store.
|
|
136
167
|
all_nodes: All nodes dict for child lookup.
|
|
168
|
+
node_alias_key: Key to resolve display name from task arguments.
|
|
137
169
|
|
|
138
170
|
Returns:
|
|
139
171
|
GraphNodeResponse with timing from children.
|
|
@@ -159,15 +191,25 @@ def _node_to_graph_response(
|
|
|
159
191
|
if first_seen and last_updated and first_seen != last_updated:
|
|
160
192
|
duration_ms = int((last_updated - first_seen).total_seconds() * 1000)
|
|
161
193
|
|
|
194
|
+
# UI traversal is order-sensitive when callbacks can appear as both direct
|
|
195
|
+
# children and CHORD-linked nodes. Prefer TASK children first so callback
|
|
196
|
+
# branches render before synthetic containers.
|
|
197
|
+
children = node.children
|
|
198
|
+
if all_nodes is not None:
|
|
199
|
+
children = sorted(
|
|
200
|
+
node.children,
|
|
201
|
+
key=lambda c: getattr(all_nodes.get(c), "node_type", None) != "TASK",
|
|
202
|
+
)
|
|
203
|
+
|
|
162
204
|
return GraphNodeResponse(
|
|
163
205
|
task_id=node.task_id,
|
|
164
|
-
name=node
|
|
206
|
+
name=_resolve_node_alias(node, node_alias_key),
|
|
165
207
|
state=node.state,
|
|
166
208
|
node_type=node.node_type,
|
|
167
209
|
group_id=node.group_id,
|
|
168
210
|
chord_id=node.chord_id,
|
|
169
211
|
parent_id=node.parent_id,
|
|
170
|
-
children=
|
|
212
|
+
children=children,
|
|
171
213
|
duration_ms=duration_ms,
|
|
172
214
|
first_seen=first_seen,
|
|
173
215
|
last_updated=last_updated,
|
|
@@ -289,6 +331,7 @@ def create_api_router(
|
|
|
289
331
|
ws_manager: WebSocketManager | None = None,
|
|
290
332
|
worker_registry: WorkerRegistry | None = None,
|
|
291
333
|
broker_url: str | None = None,
|
|
334
|
+
node_alias_from_arguments: str | None = None,
|
|
292
335
|
) -> APIRouter:
|
|
293
336
|
"""Create REST API router with task and graph endpoints.
|
|
294
337
|
|
|
@@ -298,6 +341,8 @@ def create_api_router(
|
|
|
298
341
|
ws_manager: Optional WebSocket manager.
|
|
299
342
|
worker_registry: Optional worker registry for lifecycle tracking.
|
|
300
343
|
broker_url: Optional Celery broker URL for on-demand inspection.
|
|
344
|
+
node_alias_from_arguments: Key to derive node display name from task
|
|
345
|
+
arguments. Digit string for args[index], string for kwargs[key].
|
|
301
346
|
|
|
302
347
|
Returns:
|
|
303
348
|
Configured API router.
|
|
@@ -562,7 +607,12 @@ def create_api_router(
|
|
|
562
607
|
for child in store.get_children(root.task_id):
|
|
563
608
|
all_nodes[child.task_id] = child
|
|
564
609
|
return GraphListResponse(
|
|
565
|
-
graphs=[
|
|
610
|
+
graphs=[
|
|
611
|
+
_node_to_graph_response(
|
|
612
|
+
r, all_nodes, node_alias_key=node_alias_from_arguments
|
|
613
|
+
)
|
|
614
|
+
for r in roots
|
|
615
|
+
],
|
|
566
616
|
total=total,
|
|
567
617
|
limit=limit,
|
|
568
618
|
offset=offset,
|
|
@@ -586,7 +636,10 @@ def create_api_router(
|
|
|
586
636
|
return GraphResponse(
|
|
587
637
|
root_id=root_id,
|
|
588
638
|
nodes={
|
|
589
|
-
tid: _node_to_graph_response(
|
|
639
|
+
tid: _node_to_graph_response(
|
|
640
|
+
n, all_nodes, node_alias_key=node_alias_from_arguments
|
|
641
|
+
)
|
|
642
|
+
for tid, n in graph.items()
|
|
590
643
|
},
|
|
591
644
|
)
|
|
592
645
|
|
|
@@ -67,6 +67,7 @@ class StemtraceExtension:
|
|
|
67
67
|
max_nodes: int = 10000,
|
|
68
68
|
auth_dependency: Any = None,
|
|
69
69
|
form_auth_config: FormAuthConfig | None = None,
|
|
70
|
+
node_alias_from_arguments: str | None = None,
|
|
70
71
|
) -> None:
|
|
71
72
|
"""Initialize extension with broker and transport configuration.
|
|
72
73
|
|
|
@@ -81,6 +82,8 @@ class StemtraceExtension:
|
|
|
81
82
|
max_nodes: Maximum number of nodes to keep in memory.
|
|
82
83
|
auth_dependency: Optional FastAPI dependency applied to all routes for authentication.
|
|
83
84
|
form_auth_config: Optional cookie-session configuration used to protect WebSocket.
|
|
85
|
+
node_alias_from_arguments: Key to derive graph node display name from task
|
|
86
|
+
arguments. Digit string for args[index], string for kwargs[key].
|
|
84
87
|
"""
|
|
85
88
|
self._broker_url = broker_url
|
|
86
89
|
self._transport_url = transport_url or broker_url
|
|
@@ -103,6 +106,8 @@ class StemtraceExtension:
|
|
|
103
106
|
worker_registry=self._worker_registry,
|
|
104
107
|
)
|
|
105
108
|
|
|
109
|
+
self._node_alias_from_arguments = node_alias_from_arguments
|
|
110
|
+
|
|
106
111
|
self._store.add_listener(self._ws_manager.queue_event)
|
|
107
112
|
|
|
108
113
|
@staticmethod
|
|
@@ -141,6 +146,7 @@ class StemtraceExtension:
|
|
|
141
146
|
broker_url=self._broker_url,
|
|
142
147
|
auth_dependency=self._auth_dependency,
|
|
143
148
|
form_auth_config=self._form_auth_config,
|
|
149
|
+
node_alias_from_arguments=self._node_alias_from_arguments,
|
|
144
150
|
)
|
|
145
151
|
|
|
146
152
|
if self._serve_ui:
|
|
@@ -24,6 +24,7 @@ def create_router(
|
|
|
24
24
|
broker_url: str | None = None,
|
|
25
25
|
auth_dependency: Any = None,
|
|
26
26
|
form_auth_config: FormAuthConfig | None = None,
|
|
27
|
+
node_alias_from_arguments: str | None = None,
|
|
27
28
|
) -> APIRouter:
|
|
28
29
|
"""Create API router. For embedded consumer, use StemtraceExtension."""
|
|
29
30
|
if store is None:
|
|
@@ -40,6 +41,7 @@ def create_router(
|
|
|
40
41
|
ws_manager,
|
|
41
42
|
worker_registry,
|
|
42
43
|
broker_url=broker_url,
|
|
44
|
+
node_alias_from_arguments=node_alias_from_arguments,
|
|
43
45
|
)
|
|
44
46
|
ws_router = create_websocket_router(ws_manager, form_auth_config=form_auth_config)
|
|
45
47
|
|