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.
Files changed (139) hide show
  1. {stemtrace-0.3.1 → stemtrace-0.3.3}/.bumpversion.toml +1 -1
  2. {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/workflows/ci.yml +1 -1
  3. {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/workflows/e2e.yml +4 -4
  4. {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/workflows/release.yml +10 -6
  5. {stemtrace-0.3.1 → stemtrace-0.3.3}/CHANGELOG.md +14 -0
  6. {stemtrace-0.3.1 → stemtrace-0.3.3}/PKG-INFO +6 -2
  7. {stemtrace-0.3.1 → stemtrace-0.3.3}/README.md +5 -1
  8. {stemtrace-0.3.1 → stemtrace-0.3.3}/pyproject.toml +1 -1
  9. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/__init__.py +13 -1
  10. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/config.py +6 -0
  11. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/redis.py +29 -0
  12. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/routes.py +57 -4
  13. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/extension.py +6 -0
  14. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/router.py +2 -0
  15. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/package-lock.json +313 -96
  16. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/package.json +5 -5
  17. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_init.py +2 -0
  18. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_routes.py +141 -0
  19. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_transports.py +38 -1
  20. {stemtrace-0.3.1 → stemtrace-0.3.3}/uv.lock +1 -1
  21. {stemtrace-0.3.1 → stemtrace-0.3.3}/.dockerignore +0 -0
  22. {stemtrace-0.3.1 → stemtrace-0.3.3}/.github/dependabot.yml +0 -0
  23. {stemtrace-0.3.1 → stemtrace-0.3.3}/.gitignore +0 -0
  24. {stemtrace-0.3.1 → stemtrace-0.3.3}/.pre-commit-config.yaml +0 -0
  25. {stemtrace-0.3.1 → stemtrace-0.3.3}/.python-version +0 -0
  26. {stemtrace-0.3.1 → stemtrace-0.3.3}/CONTRIBUTING.md +0 -0
  27. {stemtrace-0.3.1 → stemtrace-0.3.3}/Dockerfile +0 -0
  28. {stemtrace-0.3.1 → stemtrace-0.3.3}/Dockerfile.e2e +0 -0
  29. {stemtrace-0.3.1 → stemtrace-0.3.3}/LICENSE +0 -0
  30. {stemtrace-0.3.1 → stemtrace-0.3.3}/Makefile +0 -0
  31. {stemtrace-0.3.1 → stemtrace-0.3.3}/build_ui.py +0 -0
  32. {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.e2e.rabbitmq.yml +0 -0
  33. {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.e2e.yml +0 -0
  34. {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.rabbitmq.yml +0 -0
  35. {stemtrace-0.3.1 → stemtrace-0.3.3}/docker-compose.yml +0 -0
  36. {stemtrace-0.3.1 → stemtrace-0.3.3}/docs/screenshots/task_details.png +0 -0
  37. {stemtrace-0.3.1 → stemtrace-0.3.3}/docs/screenshots/unregistered.png +0 -0
  38. {stemtrace-0.3.1 → stemtrace-0.3.3}/docs/screenshots/workflow.png +0 -0
  39. {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/celery_app.py +0 -0
  40. {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/fastapi_integration.py +0 -0
  41. {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/with_auth.py +0 -0
  42. {stemtrace-0.3.1 → stemtrace-0.3.3}/examples/with_login.py +0 -0
  43. {stemtrace-0.3.1 → stemtrace-0.3.3}/progress.md +0 -0
  44. {stemtrace-0.3.1 → stemtrace-0.3.3}/scripts/wait_for_http.py +0 -0
  45. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/__init__.py +0 -0
  46. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/events.py +0 -0
  47. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/exceptions.py +0 -0
  48. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/graph.py +0 -0
  49. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/core/ports.py +0 -0
  50. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/__init__.py +0 -0
  51. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/bootsteps.py +0 -0
  52. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/scrubbing.py +0 -0
  53. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/signals.py +0 -0
  54. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/__init__.py +0 -0
  55. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/memory.py +0 -0
  56. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/library/transports/rabbitmq.py +0 -0
  57. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/py.typed +0 -0
  58. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/__init__.py +0 -0
  59. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/__main__.py +0 -0
  60. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/__init__.py +0 -0
  61. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/schemas.py +0 -0
  62. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/api/websocket.py +0 -0
  63. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/consumer.py +0 -0
  64. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/__init__.py +0 -0
  65. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/auth.py +0 -0
  66. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/form_auth.py +0 -0
  67. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/fastapi/login_routes.py +0 -0
  68. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/store.py +0 -0
  69. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/__init__.py +0 -0
  70. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/biome.json +0 -0
  71. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/index.html +0 -0
  72. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
  73. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
  74. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/api/client.ts +0 -0
  75. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
  76. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
  77. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
  78. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
  79. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
  80. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
  81. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
  82. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +0 -0
  83. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
  84. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +0 -0
  85. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
  86. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
  87. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
  88. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +0 -0
  89. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
  90. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
  91. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
  92. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
  93. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +0 -0
  94. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
  95. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
  96. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
  97. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
  98. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +0 -0
  99. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
  100. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
  101. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +0 -0
  102. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
  103. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
  104. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
  105. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
  106. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
  107. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/static.py +0 -0
  108. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/ui/templates/login.html +0 -0
  109. {stemtrace-0.3.1 → stemtrace-0.3.3}/src/stemtrace/server/websocket.py +0 -0
  110. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/__init__.py +0 -0
  111. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/conftest.py +0 -0
  112. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/__init__.py +0 -0
  113. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/conftest.py +0 -0
  114. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/tasks.py +0 -0
  115. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/test_api_e2e.py +0 -0
  116. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/e2e/test_workers_e2e.py +0 -0
  117. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/__init__.py +0 -0
  118. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_embedded_static.py +0 -0
  119. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_fastapi_integration.py +0 -0
  120. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_rabbitmq_transport.py +0 -0
  121. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_redis_transport.py +0 -0
  122. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_static_ui.py +0 -0
  123. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/integration/test_workers_refresh.py +0 -0
  124. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/__init__.py +0 -0
  125. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_auth.py +0 -0
  126. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_bootsteps.py +0 -0
  127. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_config.py +0 -0
  128. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_consumer.py +0 -0
  129. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_events.py +0 -0
  130. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_examples.py +0 -0
  131. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_exceptions.py +0 -0
  132. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_form_auth.py +0 -0
  133. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_form_login.py +0 -0
  134. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_graph.py +0 -0
  135. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_scrubbing.py +0 -0
  136. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_signals.py +0 -0
  137. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_store.py +0 -0
  138. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_ui_static_prefix.py +0 -0
  139. {stemtrace-0.3.1 → stemtrace-0.3.3}/tests/unit/test_websocket.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.3.1"
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,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.1
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.1-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.1-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.1"
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.1"
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.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