stemtrace 0.3.2__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.
Files changed (139) hide show
  1. {stemtrace-0.3.2 → stemtrace-0.3.3}/.bumpversion.toml +1 -1
  2. {stemtrace-0.3.2 → stemtrace-0.3.3}/.github/workflows/ci.yml +1 -1
  3. {stemtrace-0.3.2 → stemtrace-0.3.3}/.github/workflows/e2e.yml +4 -4
  4. {stemtrace-0.3.2 → stemtrace-0.3.3}/.github/workflows/release.yml +10 -6
  5. {stemtrace-0.3.2 → stemtrace-0.3.3}/CHANGELOG.md +9 -0
  6. {stemtrace-0.3.2 → stemtrace-0.3.3}/PKG-INFO +6 -2
  7. {stemtrace-0.3.2 → stemtrace-0.3.3}/README.md +5 -1
  8. {stemtrace-0.3.2 → stemtrace-0.3.3}/pyproject.toml +1 -1
  9. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/__init__.py +10 -1
  10. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/config.py +6 -0
  11. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/api/routes.py +57 -4
  12. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/extension.py +6 -0
  13. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/router.py +2 -0
  14. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/package-lock.json +313 -96
  15. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/package.json +5 -5
  16. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_init.py +2 -0
  17. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_routes.py +141 -0
  18. {stemtrace-0.3.2 → stemtrace-0.3.3}/uv.lock +1 -1
  19. {stemtrace-0.3.2 → stemtrace-0.3.3}/.dockerignore +0 -0
  20. {stemtrace-0.3.2 → stemtrace-0.3.3}/.github/dependabot.yml +0 -0
  21. {stemtrace-0.3.2 → stemtrace-0.3.3}/.gitignore +0 -0
  22. {stemtrace-0.3.2 → stemtrace-0.3.3}/.pre-commit-config.yaml +0 -0
  23. {stemtrace-0.3.2 → stemtrace-0.3.3}/.python-version +0 -0
  24. {stemtrace-0.3.2 → stemtrace-0.3.3}/CONTRIBUTING.md +0 -0
  25. {stemtrace-0.3.2 → stemtrace-0.3.3}/Dockerfile +0 -0
  26. {stemtrace-0.3.2 → stemtrace-0.3.3}/Dockerfile.e2e +0 -0
  27. {stemtrace-0.3.2 → stemtrace-0.3.3}/LICENSE +0 -0
  28. {stemtrace-0.3.2 → stemtrace-0.3.3}/Makefile +0 -0
  29. {stemtrace-0.3.2 → stemtrace-0.3.3}/build_ui.py +0 -0
  30. {stemtrace-0.3.2 → stemtrace-0.3.3}/docker-compose.e2e.rabbitmq.yml +0 -0
  31. {stemtrace-0.3.2 → stemtrace-0.3.3}/docker-compose.e2e.yml +0 -0
  32. {stemtrace-0.3.2 → stemtrace-0.3.3}/docker-compose.rabbitmq.yml +0 -0
  33. {stemtrace-0.3.2 → stemtrace-0.3.3}/docker-compose.yml +0 -0
  34. {stemtrace-0.3.2 → stemtrace-0.3.3}/docs/screenshots/task_details.png +0 -0
  35. {stemtrace-0.3.2 → stemtrace-0.3.3}/docs/screenshots/unregistered.png +0 -0
  36. {stemtrace-0.3.2 → stemtrace-0.3.3}/docs/screenshots/workflow.png +0 -0
  37. {stemtrace-0.3.2 → stemtrace-0.3.3}/examples/celery_app.py +0 -0
  38. {stemtrace-0.3.2 → stemtrace-0.3.3}/examples/fastapi_integration.py +0 -0
  39. {stemtrace-0.3.2 → stemtrace-0.3.3}/examples/with_auth.py +0 -0
  40. {stemtrace-0.3.2 → stemtrace-0.3.3}/examples/with_login.py +0 -0
  41. {stemtrace-0.3.2 → stemtrace-0.3.3}/progress.md +0 -0
  42. {stemtrace-0.3.2 → stemtrace-0.3.3}/scripts/wait_for_http.py +0 -0
  43. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/core/__init__.py +0 -0
  44. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/core/events.py +0 -0
  45. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/core/exceptions.py +0 -0
  46. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/core/graph.py +0 -0
  47. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/core/ports.py +0 -0
  48. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/__init__.py +0 -0
  49. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/bootsteps.py +0 -0
  50. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/scrubbing.py +0 -0
  51. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/signals.py +0 -0
  52. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/transports/__init__.py +0 -0
  53. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/transports/memory.py +0 -0
  54. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/transports/rabbitmq.py +0 -0
  55. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/library/transports/redis.py +0 -0
  56. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/py.typed +0 -0
  57. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/__init__.py +0 -0
  58. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/__main__.py +0 -0
  59. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/api/__init__.py +0 -0
  60. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/api/schemas.py +0 -0
  61. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/api/websocket.py +0 -0
  62. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/consumer.py +0 -0
  63. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/__init__.py +0 -0
  64. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/auth.py +0 -0
  65. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/form_auth.py +0 -0
  66. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/login_routes.py +0 -0
  67. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/store.py +0 -0
  68. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/__init__.py +0 -0
  69. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/biome.json +0 -0
  70. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/index.html +0 -0
  71. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
  72. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
  73. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/api/client.ts +0 -0
  74. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
  75. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
  76. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
  77. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
  78. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
  79. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
  80. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
  81. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
  82. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
  83. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +0 -0
  84. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
  85. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
  86. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
  87. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
  88. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
  89. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
  90. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
  91. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
  92. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +0 -0
  93. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
  94. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
  95. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
  96. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
  97. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +0 -0
  98. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
  99. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
  100. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +0 -0
  101. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
  102. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
  103. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
  104. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
  105. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
  106. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/static.py +0 -0
  107. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/ui/templates/login.html +0 -0
  108. {stemtrace-0.3.2 → stemtrace-0.3.3}/src/stemtrace/server/websocket.py +0 -0
  109. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/__init__.py +0 -0
  110. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/conftest.py +0 -0
  111. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/e2e/__init__.py +0 -0
  112. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/e2e/conftest.py +0 -0
  113. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/e2e/tasks.py +0 -0
  114. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/e2e/test_api_e2e.py +0 -0
  115. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/e2e/test_workers_e2e.py +0 -0
  116. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/__init__.py +0 -0
  117. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/test_embedded_static.py +0 -0
  118. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/test_fastapi_integration.py +0 -0
  119. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/test_rabbitmq_transport.py +0 -0
  120. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/test_redis_transport.py +0 -0
  121. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/test_static_ui.py +0 -0
  122. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/integration/test_workers_refresh.py +0 -0
  123. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/__init__.py +0 -0
  124. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_auth.py +0 -0
  125. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_bootsteps.py +0 -0
  126. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_config.py +0 -0
  127. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_consumer.py +0 -0
  128. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_events.py +0 -0
  129. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_examples.py +0 -0
  130. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_exceptions.py +0 -0
  131. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_form_auth.py +0 -0
  132. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_form_login.py +0 -0
  133. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_graph.py +0 -0
  134. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_scrubbing.py +0 -0
  135. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_signals.py +0 -0
  136. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_store.py +0 -0
  137. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_transports.py +0 -0
  138. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_ui_static_prefix.py +0 -0
  139. {stemtrace-0.3.2 → stemtrace-0.3.3}/tests/unit/test_websocket.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.3.2"
2
+ current_version = "0.3.3"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  commit = true
@@ -152,7 +152,7 @@ jobs:
152
152
  run: uvx twine check dist/*
153
153
 
154
154
  - name: Upload dist artifacts
155
- uses: actions/upload-artifact@v6
155
+ uses: actions/upload-artifact@v7
156
156
  with:
157
157
  name: dist
158
158
  path: dist/
@@ -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@v3
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@v3
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@v6
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@v6
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@v6
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@v7
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@v3
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@v5
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@v6
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@v7
149
+ uses: actions/download-artifact@v8
146
150
  with:
147
151
  name: dist
148
152
  path: dist/
@@ -7,6 +7,15 @@ 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
+
10
19
  ## [0.3.2] - 2026-02-28
11
20
 
12
21
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stemtrace
3
- Version: 0.3.2
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
- [![PyPI version](https://img.shields.io/badge/pypi-v0.3.2-darklime)](https://pypi.org/project/stemtrace)
59
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.3.3-darklime)](https://pypi.org/project/stemtrace)
60
60
  [![Python](https://img.shields.io/pypi/pyversions/stemtrace.svg)](https://pypi.org/project/stemtrace/)
61
61
  [![CI](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml/badge.svg)](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
62
62
  [![codecov](https://codecov.io/gh/iansokolskyi/stemtrace/graph/badge.svg)](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
- [![PyPI version](https://img.shields.io/badge/pypi-v0.3.2-darklime)](https://pypi.org/project/stemtrace)
5
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.3.3-darklime)](https://pypi.org/project/stemtrace)
6
6
  [![Python](https://img.shields.io/pypi/pyversions/stemtrace.svg)](https://pypi.org/project/stemtrace/)
7
7
  [![CI](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml/badge.svg)](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
8
8
  [![codecov](https://codecov.io/gh/iansokolskyi/stemtrace/graph/badge.svg)](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)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stemtrace"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "A lightweight Celery task flow visualizer"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -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.2"
46
+ __version__ = "0.3.3"
47
47
  __all__ = [
48
48
  "ConfigurationError",
49
49
  "StemtraceConfig",
@@ -93,6 +93,7 @@ def init_worker(
93
93
  additional_sensitive_keys: frozenset[str] | None = None,
94
94
  safe_keys: frozenset[str] | None = None,
95
95
  max_data_size: int = 10240,
96
+ node_alias_from_arguments: str | None = None,
96
97
  ) -> None:
97
98
  """Initialize stemtrace for Celery worker instrumentation.
98
99
 
@@ -107,6 +108,9 @@ def init_worker(
107
108
  additional_sensitive_keys: Extra keys to treat as sensitive.
108
109
  safe_keys: Keys to never scrub (overrides sensitive).
109
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).
110
114
 
111
115
  Raises:
112
116
  ConfigurationError: If no broker URL can be determined.
@@ -130,6 +134,7 @@ def init_worker(
130
134
  additional_sensitive_keys=additional_sensitive_keys or frozenset(),
131
135
  safe_keys=safe_keys or frozenset(),
132
136
  max_data_size=max_data_size,
137
+ node_alias_from_arguments=node_alias_from_arguments,
133
138
  )
134
139
  set_config(config)
135
140
 
@@ -153,6 +158,7 @@ def init_app(
153
158
  login_password: str | None = None,
154
159
  login_secret: str | None = None,
155
160
  login_ttl_seconds: int = 86400,
161
+ node_alias_from_arguments: str | None = None,
156
162
  ) -> StemtraceExtension:
157
163
  """Initialize stemtrace as a FastAPI extension.
158
164
 
@@ -174,6 +180,8 @@ def init_app(
174
180
  STEMTRACE_LOGIN_SECRET env var. If still missing, a random secret is
175
181
  generated (sessions invalidated on app restart).
176
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).
177
185
 
178
186
  Returns:
179
187
  The initialized StemtraceExtension instance.
@@ -230,6 +238,7 @@ def init_app(
230
238
  max_nodes=max_nodes,
231
239
  auth_dependency=auth_dependency,
232
240
  form_auth_config=form_auth_config,
241
+ node_alias_from_arguments=node_alias_from_arguments,
233
242
  )
234
243
  extension.init_app(fastapi_app, prefix=prefix)
235
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
 
@@ -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.name,
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=node.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=[_node_to_graph_response(r, all_nodes) for r in roots],
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(n, all_nodes) for tid, n in graph.items()
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