DefStream 0.1.3__tar.gz → 0.1.3.4__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 (81) hide show
  1. defstream-0.1.3.4/.github/workflows/ci.yml +403 -0
  2. defstream-0.1.3.4/.gitignore +62 -0
  3. {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/PKG-INFO +69 -8
  4. {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/SOURCES.txt +9 -2
  5. {defstream-0.1.3 → defstream-0.1.3.4}/PKG-INFO +69 -8
  6. {defstream-0.1.3 → defstream-0.1.3.4}/README.md +67 -7
  7. defstream-0.1.3.4/sample/Broker/Sample.py +227 -0
  8. defstream-0.1.3.4/sample/Worker/RabbitMQ/Client.py +77 -0
  9. defstream-0.1.3.4/sample/Worker/RabbitMQ/Tasks.py +120 -0
  10. defstream-0.1.3.4/sample/Worker/RabbitMQ/Worker.py +119 -0
  11. defstream-0.1.3.4/sample/Worker/Redis/Sample.py +140 -0
  12. defstream-0.1.3.4/sample/Worker/Sample.py +237 -0
  13. {defstream-0.1.3 → defstream-0.1.3.4}/setup.py +7 -1
  14. defstream-0.1.3.4/test/test_dashboard_ui.cjs +161 -0
  15. defstream-0.1.3/MANIFEST.in +0 -1
  16. defstream-0.1.3/VERSION +0 -1
  17. {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/dependency_links.txt +0 -0
  18. {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/requires.txt +0 -0
  19. {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/top_level.txt +0 -0
  20. {defstream-0.1.3 → defstream-0.1.3.4}/LICENSE +0 -0
  21. {defstream-0.1.3 → defstream-0.1.3.4}/setup.cfg +0 -0
  22. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Configuration.py +0 -0
  23. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Connection.py +0 -0
  24. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Consumer.py +0 -0
  25. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Errors.py +0 -0
  26. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Event.py +0 -0
  27. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Implements/__init__.py +0 -0
  28. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Policies.py +0 -0
  29. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Protocol.py +0 -0
  30. {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/__init__.py +0 -0
  31. {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/DataObject.py +0 -0
  32. {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/Types.py +0 -0
  33. {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/Value.py +0 -0
  34. {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/__init__.py +0 -0
  35. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extension.py +0 -0
  36. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Console.html +0 -0
  37. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Console.py +0 -0
  38. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Dashboard.html +0 -0
  39. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Dashboard.py +0 -0
  40. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/HealthCheck.py +0 -0
  41. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Metrics.py +0 -0
  42. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Specifications.py +0 -0
  43. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/__init__.py +0 -0
  44. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Inspector.py +0 -0
  45. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Response.py +0 -0
  46. {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/__init__.py +0 -0
  47. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Pattern.py +0 -0
  48. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Array.py +0 -0
  49. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Common.py +0 -0
  50. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Compare.py +0 -0
  51. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Condition.py +0 -0
  52. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/DataObject.py +0 -0
  53. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/DateTime.py +0 -0
  54. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Number.py +0 -0
  55. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Object.py +0 -0
  56. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Size.py +0 -0
  57. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/String.py +0 -0
  58. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Type.py +0 -0
  59. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/__init__.py +0 -0
  60. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Validate.py +0 -0
  61. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Validator.py +0 -0
  62. {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/__init__.py +0 -0
  63. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Executor.py +0 -0
  64. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Message.py +0 -0
  65. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Pool.py +0 -0
  66. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Run.py +0 -0
  67. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Specification.py +0 -0
  68. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Worker.py +0 -0
  69. {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/__init__.py +0 -0
  70. {defstream-0.1.3 → defstream-0.1.3.4}/src/__init__.py +0 -0
  71. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_dashboard.py +0 -0
  72. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_inspector.py +0 -0
  73. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_inspector_console.py +0 -0
  74. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_lifecycle_hooks.py +0 -0
  75. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_pool_callbacks.py +0 -0
  76. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_process_signals.py +0 -0
  77. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_protocol_contracts.py +0 -0
  78. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_runner_hooks.py +0 -0
  79. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_specifications.py +0 -0
  80. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_task_messages.py +0 -0
  81. {defstream-0.1.3 → defstream-0.1.3.4}/test/test_worker.py +0 -0
@@ -0,0 +1,403 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [master]
6
+ push:
7
+ branches: [master]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ env:
13
+ RELEASE_SERIES: '0.1'
14
+
15
+ concurrency:
16
+ group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ changes:
21
+ name: Plan release version
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 5
24
+ outputs:
25
+ release: ${{ steps.changes.outputs.release }}
26
+ version: ${{ steps.changes.outputs.version }}
27
+ steps:
28
+ - uses: actions/checkout@v7
29
+ with:
30
+ fetch-depth: 0
31
+ persist-credentials: false
32
+ - name: Set up release planner Python
33
+ uses: actions/setup-python@v7
34
+ with:
35
+ python-version: '3.13'
36
+ - name: Check complete push range and plan version
37
+ id: changes
38
+ env:
39
+ BEFORE: ${{ github.event.before }}
40
+ RELEASE_PUSH: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
41
+ run: |
42
+ python - <<'PY'
43
+ from os import environ
44
+ from pathlib import Path
45
+ from re import fullmatch
46
+ from subprocess import check_output
47
+
48
+ if environ['RELEASE_PUSH'] != 'true':
49
+ with Path(environ['GITHUB_OUTPUT']).open('a', encoding='utf-8') as output:
50
+ output.write('release=false\n')
51
+ raise SystemExit(0)
52
+ series = environ['RELEASE_SERIES']
53
+ if fullmatch(r'(0|[1-9]\d*)\.(0|[1-9]\d*)', series) is None:
54
+ raise ValueError(f'Invalid RELEASE_SERIES: {series!r}')
55
+ before = environ['BEFORE']
56
+ if not before:
57
+ raise ValueError('Missing push base commit')
58
+ first_push = set(before) == {'0'}
59
+ base = check_output(['git', 'hash-object', '-t', 'tree', '--stdin'], input='', text=True).strip() if first_push else before
60
+ changed = check_output(
61
+ ['git', 'diff', '--name-only', '--no-renames', base, environ['GITHUB_SHA'], '--', 'src/'],
62
+ text=True,
63
+ ).strip()
64
+ # Anchor to the pre-push history so a rerun ignores its own release tag.
65
+ tags = [] if first_push else check_output(['git', 'tag', '--merged', before], text=True).splitlines()
66
+ releases = []
67
+ for tag in tags:
68
+ match = fullmatch(r'v?(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?(?:\.post\d+)?', tag)
69
+ if match and f'{int(match[1])}.{int(match[2])}' == series:
70
+ releases.append(tuple(map(int, match.groups()[:3])) + (int(match[4] or 0),))
71
+ latest = max(releases) if releases else None
72
+ number = int(environ['GITHUB_RUN_NUMBER'])
73
+ if number < 1:
74
+ raise ValueError('Build number must be positive')
75
+ if latest is not None and number <= latest[3]:
76
+ raise ValueError('Build number must exceed the previous build; do not reset the workflow counter')
77
+ if changed:
78
+ patch = latest[2] + 1 if latest is not None else 0
79
+ print(f'Source changes:\n{changed}')
80
+ else:
81
+ if latest is None:
82
+ raise ValueError(
83
+ 'No release tag in pre-push history for this series. '
84
+ 'Tag the exact source commit of the existing PyPI release before retrying.'
85
+ )
86
+ patch = latest[2]
87
+ print('No src changes: keeping MAJOR.MINOR.PATCH and increasing BUILD')
88
+ version = f'{series}.{patch}.{number}'
89
+ print(f'Planned version: {version}')
90
+ with Path(environ['GITHUB_OUTPUT']).open('a', encoding='utf-8') as output:
91
+ output.write(f'release=true\nversion={version}\n')
92
+ PY
93
+
94
+ test:
95
+ name: Test Python ${{ matrix.python }}
96
+ needs: changes
97
+ runs-on: ubuntu-latest
98
+ timeout-minutes: 25
99
+ strategy:
100
+ fail-fast: false
101
+ matrix:
102
+ python: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
103
+ env:
104
+ PROJECT: ${{ github.event.repository.name }}
105
+ DEFSTREAM_RABBITMQ_TESTS: '1'
106
+ DEFSTREAM_REDIS_TESTS: '1'
107
+ services:
108
+ rabbitmq:
109
+ image: rabbitmq:4.1
110
+ ports:
111
+ - 5672:5672
112
+ env:
113
+ RABBITMQ_DEFAULT_USER: guest
114
+ RABBITMQ_DEFAULT_PASS: guest
115
+ RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: '-rabbit loopback_users []'
116
+ options: >-
117
+ --health-cmd "rabbitmq-diagnostics -q ping"
118
+ --health-interval 10s
119
+ --health-timeout 10s
120
+ --health-retries 12
121
+ redis:
122
+ image: redis:7.4
123
+ ports:
124
+ - 6379:6379
125
+ options: >-
126
+ --health-cmd "redis-cli ping"
127
+ --health-interval 5s
128
+ --health-timeout 5s
129
+ --health-retries 12
130
+ steps:
131
+ - name: Check out core
132
+ uses: actions/checkout@v7
133
+ with:
134
+ repository: yong5eon/DefStream
135
+ ref: ${{ github.event.repository.name == 'DefStream' && github.sha || 'master' }}
136
+ path: packages/DefStream
137
+ fetch-depth: 0
138
+ persist-credentials: false
139
+
140
+ - name: Check out RabbitMQ adapter
141
+ uses: actions/checkout@v7
142
+ with:
143
+ repository: yong5eon/DefStream-RabbitMQ
144
+ ref: ${{ github.event.repository.name == 'DefStream-RabbitMQ' && github.sha || 'master' }}
145
+ path: packages/DefStream-RabbitMQ
146
+ fetch-depth: 0
147
+ persist-credentials: false
148
+
149
+ - name: Check out Redis adapter
150
+ uses: actions/checkout@v7
151
+ with:
152
+ repository: yong5eon/DefStream-Redis
153
+ ref: ${{ github.event.repository.name == 'DefStream-Redis' && github.sha || 'master' }}
154
+ path: packages/DefStream-Redis
155
+ fetch-depth: 0
156
+ persist-credentials: false
157
+
158
+ - name: Set up Python
159
+ uses: actions/setup-python@v7
160
+ with:
161
+ python-version: ${{ matrix.python }}
162
+
163
+ - name: Record source commits and tag candidate locally
164
+ env:
165
+ RELEASE_BUILD: ${{ needs.changes.outputs.release }}
166
+ RELEASE_VERSION: ${{ needs.changes.outputs.version }}
167
+ run: |
168
+ for package in packages/*; do
169
+ echo "$package: $(git -C "$package" rev-parse HEAD)"
170
+ done
171
+ echo "SOURCE_DATE_EPOCH=$(git -C "packages/$PROJECT" show -s --format=%ct HEAD)" >> "$GITHUB_ENV"
172
+ python - <<'PY'
173
+ from os import environ
174
+ from pathlib import Path
175
+ from re import fullmatch
176
+ from subprocess import check_output, run
177
+
178
+ project = environ['PROJECT']
179
+ if project not in ('DefStream', 'DefStream-RabbitMQ', 'DefStream-Redis'):
180
+ raise ValueError(f'Unexpected project: {project}')
181
+ if environ['RELEASE_BUILD'] != 'true':
182
+ print('Test-only build: using the existing Git-derived development version.')
183
+ raise SystemExit(0)
184
+ version = environ['RELEASE_VERSION']
185
+ if fullmatch(r'(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\.[1-9]\d*', version) is None:
186
+ raise ValueError(f'Invalid planned version: {version!r}')
187
+ path = Path('packages') / project
188
+ tag = version
189
+ tags = check_output(['git', '-C', str(path), 'tag', '--list', tag], text=True).strip()
190
+ if tags:
191
+ tagged = check_output(['git', '-C', str(path), 'rev-parse', f'{tag}^{{commit}}'], text=True).strip()
192
+ if tagged != environ['GITHUB_SHA']:
193
+ raise RuntimeError(f'{tag} already points at another commit')
194
+ else:
195
+ run(['git', '-C', str(path), 'tag', tag, environ['GITHUB_SHA']], check=True)
196
+ print(f'{project}: local candidate {tag}')
197
+ with Path(environ['GITHUB_STEP_SUMMARY']).open('a', encoding='utf-8') as summary:
198
+ summary.write(f'## {project} {version}\n\nSource: `{environ["GITHUB_SHA"]}`\n')
199
+ PY
200
+
201
+ - name: Install build tools
202
+ run: python -m pip install --upgrade build twine
203
+
204
+ - name: Build source distributions and wheels
205
+ run: |
206
+ mkdir -p dist dependency-wheels
207
+ for name in DefStream DefStream-RabbitMQ DefStream-Redis; do
208
+ if [ "$name" = "$PROJECT" ]; then
209
+ python -m build --outdir "$GITHUB_WORKSPACE/dist" "packages/$name"
210
+ else
211
+ python -m build --outdir "$GITHUB_WORKSPACE/dependency-wheels" "packages/$name"
212
+ fi
213
+ done
214
+ python -m twine check --strict dist/*
215
+
216
+ - name: Install built wheels into a clean environment
217
+ run: |
218
+ python -m venv "$RUNNER_TEMP/installed"
219
+ "$RUNNER_TEMP/installed/bin/python" -m pip install dist/*.whl dependency-wheels/*.whl
220
+ "$RUNNER_TEMP/installed/bin/python" -m pip check
221
+
222
+ - name: Test installed package
223
+ run: |
224
+ cd "$RUNNER_TEMP"
225
+ "$RUNNER_TEMP/installed/bin/python" -m unittest discover \
226
+ -s "$GITHUB_WORKSPACE/packages/$PROJECT/test" -p 'test_*.py' -v
227
+
228
+ - name: Set up Node for core UI tests
229
+ if: env.PROJECT == 'DefStream'
230
+ uses: actions/setup-node@v7
231
+ with:
232
+ node-version: '22'
233
+
234
+ - name: Test core UI
235
+ if: env.PROJECT == 'DefStream'
236
+ run: node --test packages/DefStream/test/test_dashboard_ui.cjs
237
+
238
+ - name: Preserve verified release distributions
239
+ if: needs.changes.outputs.release == 'true' && matrix.python == '3.13'
240
+ uses: actions/upload-artifact@v7
241
+ with:
242
+ name: distributions
243
+ path: dist/*
244
+ if-no-files-found: error
245
+ retention-days: 14
246
+
247
+ publish:
248
+ name: Publish PyPI
249
+ if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.changes.outputs.release == 'true'
250
+ needs: [changes, test]
251
+ runs-on: ubuntu-latest
252
+ timeout-minutes: 10
253
+ environment: pypi
254
+ permissions:
255
+ contents: write
256
+ steps:
257
+ - name: Check out release source for tagging
258
+ uses: actions/checkout@v7
259
+ with:
260
+ ref: ${{ github.sha }}
261
+ path: release-source
262
+ fetch-depth: 0
263
+
264
+ - name: Download verified distributions
265
+ uses: actions/download-artifact@v8
266
+ with:
267
+ name: distributions
268
+ path: dist
269
+
270
+ - name: Set up Python
271
+ uses: actions/setup-python@v7
272
+ with:
273
+ python-version: '3.13'
274
+
275
+ - name: Check release status
276
+ id: release
277
+ env:
278
+ PROJECT: ${{ github.event.repository.name }}
279
+ RELEASE_VERSION: ${{ needs.changes.outputs.version }}
280
+ run: |
281
+ python - <<'PY'
282
+ from email.parser import BytesParser
283
+ from json import load
284
+ from os import environ
285
+ from pathlib import Path
286
+ from urllib.error import HTTPError
287
+ from urllib.request import urlopen
288
+ from zipfile import ZipFile
289
+
290
+ wheels = list(Path('dist').glob('*.whl'))
291
+ sources = list(Path('dist').glob('*.tar.gz'))
292
+ if len(wheels) != 1 or len(sources) != 1:
293
+ raise ValueError('Expected exactly one wheel and one source distribution')
294
+ with ZipFile(wheels[0]) as archive:
295
+ metadata_path, = (name for name in archive.namelist() if name.endswith('.dist-info/METADATA'))
296
+ metadata = BytesParser().parsebytes(archive.read(metadata_path))
297
+ project = environ['PROJECT']
298
+ if metadata['Name'] != project:
299
+ raise ValueError(f'Unexpected distribution: {metadata["Name"]}')
300
+ version = metadata['Version']
301
+ expected_version = environ['RELEASE_VERSION']
302
+ if version != expected_version:
303
+ raise ValueError(f'Expected {expected_version}, got {version}')
304
+ try:
305
+ with urlopen(f'https://pypi.org/pypi/{project}/{version}/json', timeout=30) as response:
306
+ release = load(response)
307
+ except HTTPError as error:
308
+ if error.code != 404:
309
+ raise
310
+ exists = False
311
+ else:
312
+ expected = {wheels[0].name, sources[0].name}
313
+ published = {file['filename'] for file in release['urls']}
314
+ if int(environ['GITHUB_RUN_ATTEMPT']) == 1 or not expected.issubset(published):
315
+ raise RuntimeError(
316
+ f'{project} {version} already exists or is partially published. '
317
+ 'Do not overwrite it; use a new run/version or repair the release manually.'
318
+ )
319
+ print(f'{project} {version}: already published; skipping this rerun')
320
+ exists = True
321
+ with Path(environ['GITHUB_OUTPUT']).open('a', encoding='utf-8') as output:
322
+ output.write(f'publish={str(not exists).lower()}\n')
323
+ output.write(f'version={version}\n')
324
+ PY
325
+
326
+ - name: Record verified release tag
327
+ env:
328
+ VERSION: ${{ steps.release.outputs.version }}
329
+ run: |
330
+ tag="$VERSION"
331
+ cd release-source
332
+ if git show-ref --verify --quiet "refs/tags/$tag"; then
333
+ if [ "$(git rev-parse "$tag^{commit}")" != "$GITHUB_SHA" ]; then
334
+ echo "::error::$tag already points at another commit."
335
+ exit 1
336
+ fi
337
+ else
338
+ git tag "$tag" "$GITHUB_SHA"
339
+ fi
340
+ git push origin "refs/tags/$tag"
341
+
342
+ - name: Install upload tool
343
+ if: steps.release.outputs.publish == 'true'
344
+ run: python -m pip install --upgrade twine
345
+
346
+ - name: Upload to PyPI
347
+ if: steps.release.outputs.publish == 'true'
348
+ env:
349
+ TWINE_USERNAME: __token__
350
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
351
+ run: |
352
+ if [ -z "$TWINE_PASSWORD" ]; then
353
+ echo "::error::Configure PYPI_API_TOKEN in the pypi environment or repository Actions secrets."
354
+ exit 1
355
+ fi
356
+ python -m twine upload --non-interactive dist/*
357
+
358
+ - name: Publish GitHub Release with verified distributions
359
+ env:
360
+ GH_TOKEN: ${{ github.token }}
361
+ VERSION: ${{ steps.release.outputs.version }}
362
+ run: |
363
+ python - <<'PY'
364
+ from json import load
365
+ from os import environ
366
+ from pathlib import Path
367
+ from subprocess import run
368
+ from urllib.error import HTTPError
369
+ from urllib.parse import quote
370
+ from urllib.request import Request, urlopen
371
+
372
+ repository = environ['GITHUB_REPOSITORY']
373
+ version = environ['VERSION']
374
+ request = Request(
375
+ f'{environ["GITHUB_API_URL"]}/repos/{repository}/releases/tags/{quote(version, safe="")}',
376
+ headers={
377
+ 'Authorization': f'Bearer {environ["GH_TOKEN"]}',
378
+ 'Accept': 'application/vnd.github+json',
379
+ 'X-GitHub-Api-Version': '2022-11-28',
380
+ },
381
+ )
382
+ try:
383
+ with urlopen(request, timeout=30) as response:
384
+ release = load(response)
385
+ except HTTPError as error:
386
+ if error.code != 404:
387
+ raise
388
+ run([
389
+ 'gh', 'release', 'create', version, '--repo', repository,
390
+ '--verify-tag', '--title', version, '--generate-notes', '--draft',
391
+ ], check=True)
392
+ else:
393
+ if release['tag_name'] != version:
394
+ raise ValueError('GitHub Release does not match the verified version')
395
+ artifacts = sorted(str(path) for path in Path('dist').iterdir() if path.is_file())
396
+ run([
397
+ 'gh', 'release', 'upload', version, '--repo', repository,
398
+ '--clobber', *artifacts,
399
+ ], check=True)
400
+ run([
401
+ 'gh', 'release', 'edit', version, '--repo', repository, '--draft=false',
402
+ ], check=True)
403
+ PY
@@ -0,0 +1,62 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .tox/
9
+ .nox/
10
+ .coverage
11
+ .coverage.*
12
+
13
+ # Virtual environments and local environment files
14
+ .venv/
15
+ venv/
16
+ env/
17
+ .env
18
+ .env.*
19
+
20
+ # Python packaging and build artifacts
21
+ build/
22
+ dist/
23
+ *.egg-info/
24
+ *.egg
25
+ .Python
26
+
27
+ # Local broker adapter links
28
+ /src/Broker/Implements/*
29
+ !/src/Broker/Implements/__init__.py
30
+
31
+ # Operating system files
32
+ .DS_Store
33
+ Thumbs.db
34
+ Desktop.ini
35
+
36
+ # Miscellaneous files
37
+ *.log
38
+ *.tmp
39
+ *.bak
40
+ *.swp
41
+ *.swo
42
+
43
+ # Temporary files
44
+ tmp/
45
+ *.tmp
46
+ *.temp
47
+
48
+ # IDE and editor specific files
49
+ .vscode/
50
+ .idea/
51
+
52
+ # Git
53
+ .git/
54
+
55
+ # GitHub specific files
56
+ .github/
57
+ !/.github/
58
+ /.github/*
59
+ !/.github/workflows/
60
+
61
+ # GitLab specific files
62
+ .gitlab/
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.3
3
+ Version: 0.1.3.4
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
7
7
  Project-URL: Source, https://github.com/yong5eon/DefStream
8
8
  Project-URL: Issues, https://github.com/yong5eon/DefStream/issues
9
+ Project-URL: Releases, https://github.com/yong5eon/DefStream/releases
9
10
  Classifier: Programming Language :: Python :: 3.9
10
11
  Classifier: Programming Language :: Python :: 3.10
11
12
  Classifier: Programming Language :: Python :: 3.11
@@ -38,7 +39,19 @@ The `CI` workflow runs on pull requests targeting `master` and pushes to
38
39
  `master`. Python 3.9 through 3.14 build the distributions, install the wheels in
39
40
  a clean virtual environment, and run this repository's tests against RabbitMQ
40
41
  4.1 and Redis 7.4 service containers. The core JavaScript UI tests also run.
41
- Only a successful `master` push can publish to PyPI.
42
+ Every successful `master` push publishes to PyPI; PRs only run tests.
43
+ Source additions,
44
+ deletions, and renames are detected across the entire push range; the first
45
+ push compares against an empty tree. A failed or unavailable base comparison
46
+ fails the workflow rather than assuming that there were no source changes.
47
+ Release versions and tags use `MAJOR.MINOR.PATCH.BUILD`, for example
48
+ `0.1.3.123`. Changes to `src/**` increment PATCH by one; changes only to tests,
49
+ documentation, samples, workflows, or `setup.py` keep MAJOR.MINOR.PATCH.
50
+ BUILD is the Actions run number in both cases:
51
+ `0.1.3.123` -> `0.1.3.124` (docs) -> `0.1.4.125` (source).
52
+ The entire four-part value is the public PyPI version, not hidden metadata.
53
+ Every upload has a distinct version; PyPI files are never overwritten.
54
+ Older three-part and `v`-prefixed tags remain readable; new tags have no prefix.
42
55
 
43
56
  The workflow checks out the current repository's event commit and the other
44
57
  two `yong5eon/DefStream*` repositories at `master`, recording all commit IDs in
@@ -47,12 +60,48 @@ All three packages are built locally, so the first release does not depend on
47
60
  existing PyPI releases. This tests the source combination, not compatibility
48
61
  with previously published dependency versions.
49
62
 
50
- For a push, `VERSION` must contain `major.minor.patch`; the build replaces its
51
- patch component with `github.run_number` (for example, `0.1.0` becomes `0.1.42`
52
- on run 42). PR builds retain the checked-in version. No version commit, PR, or
53
- Git tag is created. Change the major/minor in `VERSION` when starting a new
54
- release series. Run numbers can have gaps; renaming/replacing this workflow or
55
- switching an existing package to this scheme requires checking for collisions.
63
+ There is no `VERSION` or `pyproject.toml` file. `setup.py` declares
64
+ `setuptools-scm` through `setup_requires` and configures the tag format.
65
+ Build with `python -m build`, which resolves that build dependency in an
66
+ isolated environment; the legacy declaration may emit a deprecation warning.
67
+ `setuptools-scm` derives the package version from Git tags using full history.
68
+ On every master push, CI creates a local candidate tag
69
+ `<RELEASE_SERIES>.<PATCH>.<github.run_number>` (for example, `0.1.4.125`).
70
+ `RELEASE_SERIES` is configured in the workflow; change it to start a new
71
+ major/minor series. PATCH is calculated from the highest matching version tag
72
+ in the pre-push history, increasing only when source files change.
73
+ The planner uses tags reachable from the push's `before` commit so reruns do
74
+ not accidentally base themselves on their own new tag. PR builds use the
75
+ existing Git-derived development version without creating candidate tags.
76
+ No version commit or version-update PR is created. A new release series starts
77
+ at PATCH 0 on a source-changing push; non-source pushes require an existing
78
+ matching release tag. Build counter regressions fail rather than uploading an
79
+ older build. Legacy `.postN` tags are still accepted when determining PATCH;
80
+ four-part numeric builds sort above post releases of a three-part base.
81
+
82
+ After all tests pass, the publish job pushes the release tag to the exact
83
+ tested commit, then uploads the verified distributions. A tag can therefore
84
+ exist before a failed upload is retried; check PyPI or the job result for
85
+ publication status. An existing tag on another commit is an error and is
86
+ never overwritten. Run numbers can have gaps; check for collisions before
87
+ renaming/replacing this workflow.
88
+
89
+ After PyPI upload succeeds (or a rerun confirms the version is already fully
90
+ published), CI creates a GitHub Release with the same version as its title and
91
+ tag, automatically generated release notes, and the verified wheel and sdist
92
+ as assets. New releases stay in draft until both assets are uploaded. If this
93
+ step fails, rerun the workflow: it reuses the existing release and replaces
94
+ matching assets with the verified files before publishing. A failed PyPI
95
+ upload never proceeds to GitHub Release creation. PyPI's project links include
96
+ the GitHub Releases page for change history. No additional GitHub token secret
97
+ is needed; release creation uses the job's write-enabled `GITHUB_TOKEN`.
98
+
99
+ Local release checkouts use their tag version. Commits after a tag produce
100
+ development versions, and dirty trees get a local version suffix. Fetch tags
101
+ with `git fetch --tags`; build from a Git checkout or the published sdist,
102
+ not a GitHub source ZIP without Git metadata. The sdist carries the version
103
+ in its package metadata and can be rebuilt without `.git`. Query installed
104
+ versions with `importlib.metadata.version('DefStream')`.
56
105
 
57
106
  After every Python matrix entry passes, the publish job downloads the exact
58
107
  Python 3.13 distributions already built and tested. A fully published version
@@ -68,6 +117,9 @@ Before enabling automatic releases:
68
117
  appropriate PyPI project scope once the project exists. Never commit tokens.
69
118
  2. Configure the `pypi` environment to allow deployments only from `master`.
70
119
  Do not require manual approval if fully automatic deployment is desired.
120
+ Allow the workflow's `GITHUB_TOKEN` to write repository contents so the
121
+ publish job can create numeric version tags. Tag rulesets must permit this
122
+ workflow for four-part version tags.
71
123
  3. Protect `master`, require PRs, and require all six `Test Python 3.x` checks.
72
124
  Do not require `Publish PyPI`, which intentionally does not run on PRs.
73
125
  4. Keep the workflow filename/name stable and review changes to workflows and
@@ -75,6 +127,15 @@ Before enabling automatic releases:
75
127
 
76
128
  Dependencies still need to be published before end users can install adapters
77
129
  from PyPI. For the initial release, publish DefStream before its adapters.
130
+ Before migrating to SCM versions, tag each already published release's exact
131
+ source commit as `<its-PyPI-version>` and push those tags. Do not point a
132
+ release tag at an unrelated newer commit. The shared CI builds sibling
133
+ `master` sources: an untagged SCM dependency can resolve to `0.1.dev...`, below
134
+ its required minimum. These initial tags supply the starting PATCH when
135
+ migration changes no source files. For example, tag the commit used to publish
136
+ `0.1.3` as `0.1.3`; a later docs-only run 123 produces `0.1.3.123`.
137
+ This tag is a historical source/version link, not a source edit or a new PyPI
138
+ upload. Never invent that link by tagging an arbitrary current commit.
78
139
 
79
140
  **Just def.**<br>
80
141
  **Let workers do the work.**<br>
@@ -1,13 +1,19 @@
1
+ .gitignore
1
2
  LICENSE
2
- MANIFEST.in
3
3
  README.md
4
- VERSION
5
4
  setup.py
5
+ .github/workflows/ci.yml
6
6
  DefStream.egg-info/PKG-INFO
7
7
  DefStream.egg-info/SOURCES.txt
8
8
  DefStream.egg-info/dependency_links.txt
9
9
  DefStream.egg-info/requires.txt
10
10
  DefStream.egg-info/top_level.txt
11
+ sample/Broker/Sample.py
12
+ sample/Worker/Sample.py
13
+ sample/Worker/RabbitMQ/Client.py
14
+ sample/Worker/RabbitMQ/Tasks.py
15
+ sample/Worker/RabbitMQ/Worker.py
16
+ sample/Worker/Redis/Sample.py
11
17
  src/__init__.py
12
18
  src/Broker/Configuration.py
13
19
  src/Broker/Connection.py
@@ -58,6 +64,7 @@ src/Worker/Specification.py
58
64
  src/Worker/Worker.py
59
65
  src/Worker/__init__.py
60
66
  test/test_dashboard.py
67
+ test/test_dashboard_ui.cjs
61
68
  test/test_inspector.py
62
69
  test/test_inspector_console.py
63
70
  test/test_lifecycle_hooks.py