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.
- defstream-0.1.3.4/.github/workflows/ci.yml +403 -0
- defstream-0.1.3.4/.gitignore +62 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/PKG-INFO +69 -8
- {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/SOURCES.txt +9 -2
- {defstream-0.1.3 → defstream-0.1.3.4}/PKG-INFO +69 -8
- {defstream-0.1.3 → defstream-0.1.3.4}/README.md +67 -7
- defstream-0.1.3.4/sample/Broker/Sample.py +227 -0
- defstream-0.1.3.4/sample/Worker/RabbitMQ/Client.py +77 -0
- defstream-0.1.3.4/sample/Worker/RabbitMQ/Tasks.py +120 -0
- defstream-0.1.3.4/sample/Worker/RabbitMQ/Worker.py +119 -0
- defstream-0.1.3.4/sample/Worker/Redis/Sample.py +140 -0
- defstream-0.1.3.4/sample/Worker/Sample.py +237 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/setup.py +7 -1
- defstream-0.1.3.4/test/test_dashboard_ui.cjs +161 -0
- defstream-0.1.3/MANIFEST.in +0 -1
- defstream-0.1.3/VERSION +0 -1
- {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/dependency_links.txt +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/requires.txt +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/DefStream.egg-info/top_level.txt +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/LICENSE +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/setup.cfg +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Configuration.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Connection.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Consumer.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Errors.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Event.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Implements/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Policies.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/Protocol.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Broker/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/DataObject.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/Types.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/Value.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Descriptor/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extension.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Console.html +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Console.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Dashboard.html +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Dashboard.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/HealthCheck.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Metrics.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/Specifications.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Extensions/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Inspector.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/Response.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Inspector/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Pattern.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Array.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Common.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Compare.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Condition.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/DataObject.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/DateTime.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Number.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Object.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Size.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/String.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/Type.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Patterns/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Validate.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/Validator.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Validator/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Executor.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Message.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Pool.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Run.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Specification.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/Worker.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/Worker/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/src/__init__.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_dashboard.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_inspector.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_inspector_console.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_lifecycle_hooks.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_pool_callbacks.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_process_signals.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_protocol_contracts.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_runner_hooks.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_specifications.py +0 -0
- {defstream-0.1.3 → defstream-0.1.3.4}/test/test_task_messages.py +0 -0
- {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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|