DefStream-RabbitMQ 0.1.1.3__tar.gz → 0.1.2.6__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.

Potentially problematic release.


This version of DefStream-RabbitMQ might be problematic. Click here for more details.

Files changed (38) hide show
  1. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/.github/workflows/ci.yml +111 -5
  2. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6/DefStream_RabbitMQ.egg-info}/PKG-INFO +43 -1
  3. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/DefStream_RabbitMQ.egg-info/SOURCES.txt +6 -0
  4. {defstream_rabbitmq-0.1.1.3/DefStream_RabbitMQ.egg-info → defstream_rabbitmq-0.1.2.6}/PKG-INFO +43 -1
  5. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/README.en.md +47 -0
  6. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/README.md +42 -0
  7. defstream_rabbitmq-0.1.2.6/issues/feature/1/IMPLEMENTS.md +93 -0
  8. defstream_rabbitmq-0.1.2.6/issues/feature/1/README.md +103 -0
  9. defstream_rabbitmq-0.1.2.6/issues/feature/1/REQUIREMENTS.md +152 -0
  10. defstream_rabbitmq-0.1.2.6/issues/feature/1/TASK.md +44 -0
  11. defstream_rabbitmq-0.1.2.6/issues/feature/1/ci.refs.json +3 -0
  12. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Configuration.py +1 -1
  13. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Connection.py +42 -10
  14. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Consumer.py +3 -5
  15. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Event.py +1 -1
  16. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Exchange.py +8 -3
  17. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Policies.py +1 -1
  18. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Queue.py +15 -3
  19. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Stream.py +1 -1
  20. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Subscription.py +27 -0
  21. defstream_rabbitmq-0.1.2.6/test/test_priorities.py +442 -0
  22. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/.gitignore +0 -0
  23. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/DefStream_RabbitMQ.egg-info/dependency_links.txt +0 -0
  24. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/DefStream_RabbitMQ.egg-info/requires.txt +0 -0
  25. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/DefStream_RabbitMQ.egg-info/top_level.txt +0 -0
  26. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/LICENSE +0 -0
  27. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/sample/Sample.RPC.py +0 -0
  28. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/sample/Sample.Stream.py +0 -0
  29. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/sample/Sample.py +0 -0
  30. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/setup.cfg +0 -0
  31. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/setup.py +0 -0
  32. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/Serializer.py +0 -0
  33. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/src/__init__.py +0 -0
  34. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/test/test_broker_methods.py +0 -0
  35. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/test/test_rabbitmq_integration.py +0 -0
  36. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/test/test_rabbitmq_worker.py +0 -0
  37. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/test/test_rpc.py +0 -0
  38. {defstream_rabbitmq-0.1.1.3 → defstream_rabbitmq-0.1.2.6}/test/test_rpc_integration.py +0 -0
@@ -19,11 +19,17 @@ concurrency:
19
19
  jobs:
20
20
  changes:
21
21
  name: Plan release version
22
+ permissions:
23
+ contents: read
24
+ pull-requests: read
22
25
  runs-on: ubuntu-latest
23
26
  timeout-minutes: 5
24
27
  outputs:
25
28
  release: ${{ steps.changes.outputs.release }}
26
29
  version: ${{ steps.changes.outputs.version }}
30
+ core_ref: ${{ steps.refs.outputs.core_ref }}
31
+ rabbitmq_ref: ${{ steps.refs.outputs.rabbitmq_ref }}
32
+ redis_ref: ${{ steps.refs.outputs.redis_ref }}
27
33
  steps:
28
34
  - uses: actions/checkout@v7
29
35
  with:
@@ -33,6 +39,96 @@ jobs:
33
39
  uses: actions/setup-python@v7
34
40
  with:
35
41
  python-version: '3.13'
42
+ - name: Validate PR branch changelog
43
+ id: changelog
44
+ env:
45
+ GH_TOKEN: ${{ github.token }}
46
+ run: |
47
+ python - <<'PY'
48
+ from json import loads
49
+ from os import environ
50
+ from pathlib import Path
51
+ from subprocess import check_output
52
+
53
+ head = environ['GITHUB_SHA']
54
+ if environ['GITHUB_EVENT_NAME'] == 'pull_request':
55
+ event = loads(Path(environ['GITHUB_EVENT_PATH']).read_text(encoding='utf-8'))
56
+ branch = event['pull_request']['head']['ref']
57
+ else:
58
+ repository = environ['GITHUB_REPOSITORY']
59
+ pages = loads(check_output([
60
+ 'gh', 'api', '--paginate', '--slurp',
61
+ f'repos/{repository}/commits/{head}/pulls?per_page=100',
62
+ ]))
63
+ pulls = [
64
+ pull for page in pages for pull in page
65
+ if pull['merged_at'] is not None
66
+ and pull['merge_commit_sha'] == head
67
+ and pull['base']['ref'] == 'master'
68
+ and pull['base']['repo']['full_name'] == repository
69
+ ]
70
+ if len(pulls) != 1:
71
+ raise ValueError('Expected exactly one merged PR for the release commit')
72
+ branch = pulls[0]['head']['ref']
73
+ path = f'issues/{branch}/README.md'
74
+ entry = check_output(['git', 'ls-tree', head, '--', path]).decode().split()
75
+ if not entry or entry[0] not in ('100644', '100755'):
76
+ raise ValueError(f'Changelog must exist as a regular file: {path}')
77
+ body = check_output(['git', 'show', f'{head}:{path}'])
78
+ if not body.decode('utf-8').strip():
79
+ raise ValueError(f'Customer changelog is empty: {path}')
80
+ notes = Path(environ['RUNNER_TEMP'], 'release-notes', 'README.md')
81
+ notes.parent.mkdir(parents=True, exist_ok=True)
82
+ notes.write_bytes(body)
83
+ print(f'Customer changelog: {path}')
84
+ with Path(environ['GITHUB_OUTPUT']).open('a', encoding='utf-8') as output:
85
+ output.write(f'branch={branch}\n')
86
+ PY
87
+ - name: Resolve dependency refs
88
+ id: refs
89
+ env:
90
+ BRANCH: ${{ steps.changelog.outputs.branch }}
91
+ run: |
92
+ python - <<'PY'
93
+ from json import loads
94
+ from os import environ
95
+ from pathlib import Path
96
+ from subprocess import check_output, run
97
+
98
+ head = environ['GITHUB_SHA']
99
+ path = f'issues/{environ["BRANCH"]}/ci.refs.json'
100
+ entry = check_output(['git', 'ls-tree', head, '--', path]).decode().split()
101
+ refs = {}
102
+ if entry:
103
+ if entry[0] not in ('100644', '100755'):
104
+ raise ValueError(f'Dependency refs must be a regular file: {path}')
105
+ refs = loads(check_output(['git', 'show', f'{head}:{path}']).decode('utf-8'))
106
+ else:
107
+ print(f'{path} not found: dependency refs default to master')
108
+ projects = {
109
+ 'DefStream': 'core_ref',
110
+ 'DefStream-RabbitMQ': 'rabbitmq_ref',
111
+ 'DefStream-Redis': 'redis_ref',
112
+ }
113
+ if not isinstance(refs, dict) or refs.keys() - projects.keys():
114
+ raise ValueError(f'Expected an object with only supported project names: {path}')
115
+ with Path(environ['GITHUB_OUTPUT']).open('a', encoding='utf-8') as output:
116
+ for project, key in projects.items():
117
+ ref = refs.get(project, 'master')
118
+ if not isinstance(ref, str) or not ref or ref.startswith('-'):
119
+ raise ValueError(f'Invalid dependency ref for {project}: {ref!r}')
120
+ run(['git', 'check-ref-format', '--allow-onelevel', ref], check=True)
121
+ output.write(f'{key}={ref}\n')
122
+ print(f'{project} dependency ref: {ref}')
123
+ PY
124
+ - name: Preserve customer release notes
125
+ uses: actions/upload-artifact@v7
126
+ with:
127
+ name: release-notes
128
+ path: ${{ runner.temp }}/release-notes/README.md
129
+ if-no-files-found: error
130
+ retention-days: 14
131
+ overwrite: true
36
132
  - name: Check complete push range and plan version
37
133
  id: changes
38
134
  env:
@@ -132,7 +228,7 @@ jobs:
132
228
  uses: actions/checkout@v7
133
229
  with:
134
230
  repository: yong5eon/DefStream
135
- ref: ${{ github.event.repository.name == 'DefStream' && github.sha || 'master' }}
231
+ ref: ${{ github.event.repository.name == 'DefStream' && github.sha || needs.changes.outputs.core_ref }}
136
232
  path: packages/DefStream
137
233
  fetch-depth: 0
138
234
  persist-credentials: false
@@ -141,7 +237,7 @@ jobs:
141
237
  uses: actions/checkout@v7
142
238
  with:
143
239
  repository: yong5eon/DefStream-RabbitMQ
144
- ref: ${{ github.event.repository.name == 'DefStream-RabbitMQ' && github.sha || 'master' }}
240
+ ref: ${{ github.event.repository.name == 'DefStream-RabbitMQ' && github.sha || needs.changes.outputs.rabbitmq_ref }}
145
241
  path: packages/DefStream-RabbitMQ
146
242
  fetch-depth: 0
147
243
  persist-credentials: false
@@ -150,7 +246,7 @@ jobs:
150
246
  uses: actions/checkout@v7
151
247
  with:
152
248
  repository: yong5eon/DefStream-Redis
153
- ref: ${{ github.event.repository.name == 'DefStream-Redis' && github.sha || 'master' }}
249
+ ref: ${{ github.event.repository.name == 'DefStream-Redis' && github.sha || needs.changes.outputs.redis_ref }}
154
250
  path: packages/DefStream-Redis
155
251
  fetch-depth: 0
156
252
  persist-credentials: false
@@ -267,6 +363,12 @@ jobs:
267
363
  name: distributions
268
364
  path: dist
269
365
 
366
+ - name: Download customer release notes
367
+ uses: actions/download-artifact@v8
368
+ with:
369
+ name: release-notes
370
+ path: release-notes
371
+
270
372
  - name: Set up Python
271
373
  uses: actions/setup-python@v7
272
374
  with:
@@ -371,6 +473,9 @@ jobs:
371
473
 
372
474
  repository = environ['GITHUB_REPOSITORY']
373
475
  version = environ['VERSION']
476
+ notes = Path('release-notes/README.md')
477
+ if not notes.read_text(encoding='utf-8').strip():
478
+ raise ValueError('Customer release notes are empty')
374
479
  request = Request(
375
480
  f'{environ["GITHUB_API_URL"]}/repos/{repository}/releases/tags/{quote(version, safe="")}',
376
481
  headers={
@@ -387,7 +492,7 @@ jobs:
387
492
  raise
388
493
  run([
389
494
  'gh', 'release', 'create', version, '--repo', repository,
390
- '--verify-tag', '--title', version, '--generate-notes', '--draft',
495
+ '--verify-tag', '--title', version, '--notes-file', str(notes), '--draft',
391
496
  ], check=True)
392
497
  else:
393
498
  if release['tag_name'] != version:
@@ -398,6 +503,7 @@ jobs:
398
503
  '--clobber', *artifacts,
399
504
  ], check=True)
400
505
  run([
401
- 'gh', 'release', 'edit', version, '--repo', repository, '--draft=false',
506
+ 'gh', 'release', 'edit', version, '--repo', repository,
507
+ '--notes-file', str(notes), '--draft=false',
402
508
  ], check=True)
403
509
  PY
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream-RabbitMQ
3
- Version: 0.1.1.3
3
+ Version: 0.1.2.6
4
4
  Summary: RabbitMQ broker adapter for DefStream
5
5
  Home-page: https://github.com/yong5eon/DefStream-RabbitMQ
6
6
  Author: Heo Yongseon
@@ -176,12 +176,54 @@ Worker는 실행과 완료 훅이 끝난 뒤 ACK하고, 실패하면 지정한
176
176
  Worker의 공유 풀 크기와 별개입니다.
177
177
  - `error`: `declare()`가 생성하고 연결할 오류 큐 이름. 거부된 메시지의 DLQ입니다.
178
178
  - `offset`: RabbitMQ 스트림 구독에 사용할 정수 오프셋.
179
+ - `max_priority`: classic 작업 큐의 최대 우선순위. 정수 `1..255` 또는 `None`.
180
+ - `priority`: 기본 메시지 우선순위. 정수 `0..max_priority` 또는 `None`.
181
+ 이 값만 지정하면 `max_priority`는 `255`로 설정됩니다.
179
182
 
180
183
  `Subscription` 객체 생성이나 `consumer.subs()`는 큐를 선언하지 않습니다.
181
184
  작업용 `declare()`는 일반 durable 큐와 선택적 DLQ를 준비하며,
182
185
  스트림 큐나 Exchange 바인딩을 자동 생성하지 않습니다.
183
186
  기존 큐의 선언 옵션이 다르면 실패하며 자동 삭제·재생성하지 않습니다.
184
187
 
188
+ ## 워커 작업 우선순위
189
+
190
+ 작업에 지정한 `Subscription`에 기본값 `priority`와 큐 최대값 `max_priority`를
191
+ 함께 설정합니다. `Run`에는 별도의 `priority` 설정을 두지 않습니다.
192
+
193
+ ```python
194
+ class PriorityAdd(
195
+ Add,
196
+ parameters=Subscription(
197
+ queue='defstream.example.rabbitmq.priority.add',
198
+ qos=1,
199
+ max_priority=5,
200
+ priority=2,
201
+ ),
202
+ ):
203
+ pass
204
+ ```
205
+
206
+ - 워커 API `declare(task=PriorityAdd, broker=connection)`는 큐에
207
+ `x-max-priority=5`를 반영합니다. 기존 DLQ 설정도 보존합니다.
208
+ - `send(..., task=PriorityAdd, broker=connection)` 및
209
+ `request(..., task=PriorityAdd, broker=connection)`는 작업 기본값 `2`를 사용합니다.
210
+ 호출에 `priority=0` 또는 다른 유효한 값을 지정하여 재정의할 수 있습니다.
211
+ - 기본값만 정의하면 큐 최대값은 `255`, 최대값만 정의하면 기본값은 `None`입니다.
212
+ 호출별 값도 없으면 AMQP 우선순위 속성을 생략합니다.
213
+ - 워커 경로는 작업의 유효 최대값으로 검증합니다. 작업 설정 없이 호출에서만
214
+ 지정하면 `0..255` 범위만 검증하며, 우선순위 큐 사전 선언은 호출자 책임입니다.
215
+ - 기존 저수준 `Queue.send`, `Exchange.send`와 `Queue.call`도 `priority=`를 받습니다.
216
+ 이 경로는 작업 정의를 받지 않으므로 작업별 최대값은 검증하지 않습니다.
217
+ 정수 `0..255`만 허용하며 `bool`은 거부합니다.
218
+ - 최대값을 늘릴수록 자원 비용이 증가하므로 필요한 수준만 지정하는 것을 권장합니다.
219
+ - 동일 큐를 여러 작업 정의에서 선언한다면 최대값을 동일하게 맞춰야 합니다.
220
+ 기존 일반 큐나 최대값이 다른 큐를 재선언하면 RabbitMQ 오류가 전파됩니다.
221
+ 자동 삭제하지 않으므로, 새 큐로 전환하거나 미처리 메시지를 보존한 후 운영자가
222
+ 안전하게 재생성하고 생산자·소비자를 함께 전환해야 합니다.
223
+ - 높은 값이 먼저 전달되지만 이미 prefetch된 메시지나 실행 중인 작업은 선점하지
224
+ 않습니다. 재전달, 다중 소비자에서는 완료 순서를 보장하지 않으며 낮은 우선순위
225
+ 메시지가 오래 대기할 수 있습니다. Stream 및 Quorum 큐는 이번 지원 대상이 아닙니다.
226
+
185
227
  ## 동기 RPC
186
228
 
187
229
  실행 중인 Worker가 있는 큐에 `call()`을 사용하면 `Run.run()`의 반환값을 받습니다.
@@ -9,6 +9,11 @@ DefStream_RabbitMQ.egg-info/SOURCES.txt
9
9
  DefStream_RabbitMQ.egg-info/dependency_links.txt
10
10
  DefStream_RabbitMQ.egg-info/requires.txt
11
11
  DefStream_RabbitMQ.egg-info/top_level.txt
12
+ issues/feature/1/IMPLEMENTS.md
13
+ issues/feature/1/README.md
14
+ issues/feature/1/REQUIREMENTS.md
15
+ issues/feature/1/TASK.md
16
+ issues/feature/1/ci.refs.json
12
17
  sample/Sample.RPC.py
13
18
  sample/Sample.Stream.py
14
19
  sample/Sample.py
@@ -24,6 +29,7 @@ src/Stream.py
24
29
  src/Subscription.py
25
30
  src/__init__.py
26
31
  test/test_broker_methods.py
32
+ test/test_priorities.py
27
33
  test/test_rabbitmq_integration.py
28
34
  test/test_rabbitmq_worker.py
29
35
  test/test_rpc.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream-RabbitMQ
3
- Version: 0.1.1.3
3
+ Version: 0.1.2.6
4
4
  Summary: RabbitMQ broker adapter for DefStream
5
5
  Home-page: https://github.com/yong5eon/DefStream-RabbitMQ
6
6
  Author: Heo Yongseon
@@ -176,12 +176,54 @@ Worker는 실행과 완료 훅이 끝난 뒤 ACK하고, 실패하면 지정한
176
176
  Worker의 공유 풀 크기와 별개입니다.
177
177
  - `error`: `declare()`가 생성하고 연결할 오류 큐 이름. 거부된 메시지의 DLQ입니다.
178
178
  - `offset`: RabbitMQ 스트림 구독에 사용할 정수 오프셋.
179
+ - `max_priority`: classic 작업 큐의 최대 우선순위. 정수 `1..255` 또는 `None`.
180
+ - `priority`: 기본 메시지 우선순위. 정수 `0..max_priority` 또는 `None`.
181
+ 이 값만 지정하면 `max_priority`는 `255`로 설정됩니다.
179
182
 
180
183
  `Subscription` 객체 생성이나 `consumer.subs()`는 큐를 선언하지 않습니다.
181
184
  작업용 `declare()`는 일반 durable 큐와 선택적 DLQ를 준비하며,
182
185
  스트림 큐나 Exchange 바인딩을 자동 생성하지 않습니다.
183
186
  기존 큐의 선언 옵션이 다르면 실패하며 자동 삭제·재생성하지 않습니다.
184
187
 
188
+ ## 워커 작업 우선순위
189
+
190
+ 작업에 지정한 `Subscription`에 기본값 `priority`와 큐 최대값 `max_priority`를
191
+ 함께 설정합니다. `Run`에는 별도의 `priority` 설정을 두지 않습니다.
192
+
193
+ ```python
194
+ class PriorityAdd(
195
+ Add,
196
+ parameters=Subscription(
197
+ queue='defstream.example.rabbitmq.priority.add',
198
+ qos=1,
199
+ max_priority=5,
200
+ priority=2,
201
+ ),
202
+ ):
203
+ pass
204
+ ```
205
+
206
+ - 워커 API `declare(task=PriorityAdd, broker=connection)`는 큐에
207
+ `x-max-priority=5`를 반영합니다. 기존 DLQ 설정도 보존합니다.
208
+ - `send(..., task=PriorityAdd, broker=connection)` 및
209
+ `request(..., task=PriorityAdd, broker=connection)`는 작업 기본값 `2`를 사용합니다.
210
+ 호출에 `priority=0` 또는 다른 유효한 값을 지정하여 재정의할 수 있습니다.
211
+ - 기본값만 정의하면 큐 최대값은 `255`, 최대값만 정의하면 기본값은 `None`입니다.
212
+ 호출별 값도 없으면 AMQP 우선순위 속성을 생략합니다.
213
+ - 워커 경로는 작업의 유효 최대값으로 검증합니다. 작업 설정 없이 호출에서만
214
+ 지정하면 `0..255` 범위만 검증하며, 우선순위 큐 사전 선언은 호출자 책임입니다.
215
+ - 기존 저수준 `Queue.send`, `Exchange.send`와 `Queue.call`도 `priority=`를 받습니다.
216
+ 이 경로는 작업 정의를 받지 않으므로 작업별 최대값은 검증하지 않습니다.
217
+ 정수 `0..255`만 허용하며 `bool`은 거부합니다.
218
+ - 최대값을 늘릴수록 자원 비용이 증가하므로 필요한 수준만 지정하는 것을 권장합니다.
219
+ - 동일 큐를 여러 작업 정의에서 선언한다면 최대값을 동일하게 맞춰야 합니다.
220
+ 기존 일반 큐나 최대값이 다른 큐를 재선언하면 RabbitMQ 오류가 전파됩니다.
221
+ 자동 삭제하지 않으므로, 새 큐로 전환하거나 미처리 메시지를 보존한 후 운영자가
222
+ 안전하게 재생성하고 생산자·소비자를 함께 전환해야 합니다.
223
+ - 높은 값이 먼저 전달되지만 이미 prefetch된 메시지나 실행 중인 작업은 선점하지
224
+ 않습니다. 재전달, 다중 소비자에서는 완료 순서를 보장하지 않으며 낮은 우선순위
225
+ 메시지가 오래 대기할 수 있습니다. Stream 및 Quorum 큐는 이번 지원 대상이 아닙니다.
226
+
185
227
  ## 동기 RPC
186
228
 
187
229
  실행 중인 Worker가 있는 큐에 `call()`을 사용하면 `Run.run()`의 반환값을 받습니다.
@@ -146,12 +146,59 @@ the selected failure policy on error. Omitting `on_error` defaults to `Requeue()
146
146
  is 1; 0 means unlimited. This is separate from shared Worker pool capacity.
147
147
  - `error`: error queue to create and bind through `declare()`; a DLQ for rejected messages.
148
148
  - `offset`: integer offset for RabbitMQ stream subscriptions.
149
+ - `max_priority`: maximum priority for a classic task queue; an integer in `1..255` or `None`.
150
+ - `priority`: default message priority; an integer in `0..max_priority` or `None`.
151
+ Setting only this value sets `max_priority` to `255`.
149
152
 
150
153
  Constructing a `Subscription` or calling `consumer.subs()` does not declare queues.
151
154
  Task `declare()` provisions ordinary durable queues and an optional DLQ,
152
155
  not stream queues or Exchange bindings. Incompatible existing queue options
153
156
  fail explicitly; queues are not automatically deleted and recreated.
154
157
 
158
+ ## Worker task priorities
159
+
160
+ Set the default `priority` and queue `max_priority` together in the task's
161
+ `Subscription`. There is no separate `priority` setting on `Run`.
162
+
163
+ ```python
164
+ class PriorityAdd(
165
+ Add,
166
+ parameters=Subscription(
167
+ queue='defstream.example.rabbitmq.priority.add',
168
+ qos=1,
169
+ max_priority=5,
170
+ priority=2,
171
+ ),
172
+ ):
173
+ pass
174
+ ```
175
+
176
+ - The Worker API `declare(task=PriorityAdd, broker=connection)` declares the queue
177
+ with `x-max-priority=5` while preserving existing DLQ settings.
178
+ - `send(..., task=PriorityAdd, broker=connection)` and
179
+ `request(..., task=PriorityAdd, broker=connection)` use the task default of `2`.
180
+ Pass `priority=0` or another valid value to override it for an individual call.
181
+ - Setting only the default sets the queue maximum to `255`. Setting only the
182
+ maximum leaves the default as `None`. Without a per-call value either, the AMQP
183
+ priority property is omitted.
184
+ - Worker calls validate against the task's effective maximum. If priority is
185
+ specified only at call time, only the `0..255` range is validated; the caller
186
+ must provision a priority queue beforehand.
187
+ - The existing low-level `Queue.send`, `Exchange.send`, and `Queue.call` also
188
+ accept `priority=`. These APIs do not receive a task definition and therefore
189
+ do not validate against a task-specific maximum. They accept integers in
190
+ `0..255` and reject `bool`.
191
+ - Larger maximum values increase resource costs; configure only the levels you need.
192
+ - Task definitions declaring the same queue must use the same maximum.
193
+ Redeclaring an ordinary queue or a queue with a different maximum propagates
194
+ a RabbitMQ error. Queues are not automatically deleted. Switch to a new queue,
195
+ or preserve pending messages before safely recreating the queue and migrating
196
+ producers and consumers together.
197
+ - Higher values are delivered first, but prefetched messages and running tasks
198
+ are not preempted. Redelivery and multiple consumers prevent guarantees about
199
+ completion order, and lower-priority messages can wait indefinitely.
200
+ Streams and quorum queues are outside the scope of this feature.
201
+
155
202
  ## Synchronous RPC
156
203
 
157
204
  Call a queue served by a running Worker to receive the result of `Run.run()`.
@@ -145,12 +145,54 @@ Worker는 실행과 완료 훅이 끝난 뒤 ACK하고, 실패하면 지정한
145
145
  Worker의 공유 풀 크기와 별개입니다.
146
146
  - `error`: `declare()`가 생성하고 연결할 오류 큐 이름. 거부된 메시지의 DLQ입니다.
147
147
  - `offset`: RabbitMQ 스트림 구독에 사용할 정수 오프셋.
148
+ - `max_priority`: classic 작업 큐의 최대 우선순위. 정수 `1..255` 또는 `None`.
149
+ - `priority`: 기본 메시지 우선순위. 정수 `0..max_priority` 또는 `None`.
150
+ 이 값만 지정하면 `max_priority`는 `255`로 설정됩니다.
148
151
 
149
152
  `Subscription` 객체 생성이나 `consumer.subs()`는 큐를 선언하지 않습니다.
150
153
  작업용 `declare()`는 일반 durable 큐와 선택적 DLQ를 준비하며,
151
154
  스트림 큐나 Exchange 바인딩을 자동 생성하지 않습니다.
152
155
  기존 큐의 선언 옵션이 다르면 실패하며 자동 삭제·재생성하지 않습니다.
153
156
 
157
+ ## 워커 작업 우선순위
158
+
159
+ 작업에 지정한 `Subscription`에 기본값 `priority`와 큐 최대값 `max_priority`를
160
+ 함께 설정합니다. `Run`에는 별도의 `priority` 설정을 두지 않습니다.
161
+
162
+ ```python
163
+ class PriorityAdd(
164
+ Add,
165
+ parameters=Subscription(
166
+ queue='defstream.example.rabbitmq.priority.add',
167
+ qos=1,
168
+ max_priority=5,
169
+ priority=2,
170
+ ),
171
+ ):
172
+ pass
173
+ ```
174
+
175
+ - 워커 API `declare(task=PriorityAdd, broker=connection)`는 큐에
176
+ `x-max-priority=5`를 반영합니다. 기존 DLQ 설정도 보존합니다.
177
+ - `send(..., task=PriorityAdd, broker=connection)` 및
178
+ `request(..., task=PriorityAdd, broker=connection)`는 작업 기본값 `2`를 사용합니다.
179
+ 호출에 `priority=0` 또는 다른 유효한 값을 지정하여 재정의할 수 있습니다.
180
+ - 기본값만 정의하면 큐 최대값은 `255`, 최대값만 정의하면 기본값은 `None`입니다.
181
+ 호출별 값도 없으면 AMQP 우선순위 속성을 생략합니다.
182
+ - 워커 경로는 작업의 유효 최대값으로 검증합니다. 작업 설정 없이 호출에서만
183
+ 지정하면 `0..255` 범위만 검증하며, 우선순위 큐 사전 선언은 호출자 책임입니다.
184
+ - 기존 저수준 `Queue.send`, `Exchange.send`와 `Queue.call`도 `priority=`를 받습니다.
185
+ 이 경로는 작업 정의를 받지 않으므로 작업별 최대값은 검증하지 않습니다.
186
+ 정수 `0..255`만 허용하며 `bool`은 거부합니다.
187
+ - 최대값을 늘릴수록 자원 비용이 증가하므로 필요한 수준만 지정하는 것을 권장합니다.
188
+ - 동일 큐를 여러 작업 정의에서 선언한다면 최대값을 동일하게 맞춰야 합니다.
189
+ 기존 일반 큐나 최대값이 다른 큐를 재선언하면 RabbitMQ 오류가 전파됩니다.
190
+ 자동 삭제하지 않으므로, 새 큐로 전환하거나 미처리 메시지를 보존한 후 운영자가
191
+ 안전하게 재생성하고 생산자·소비자를 함께 전환해야 합니다.
192
+ - 높은 값이 먼저 전달되지만 이미 prefetch된 메시지나 실행 중인 작업은 선점하지
193
+ 않습니다. 재전달, 다중 소비자에서는 완료 순서를 보장하지 않으며 낮은 우선순위
194
+ 메시지가 오래 대기할 수 있습니다. Stream 및 Quorum 큐는 이번 지원 대상이 아닙니다.
195
+
154
196
  ## 동기 RPC
155
197
 
156
198
  실행 중인 Worker가 있는 큐에 `call()`을 사용하면 `Run.run()`의 반환값을 받습니다.
@@ -0,0 +1,93 @@
1
+ # RabbitMQ 작업 우선순위 구현 기록
2
+
3
+ ## 1. 구현 범위
4
+
5
+ [요구사항](./REQUIREMENTS.md)에 따라 classic 작업 큐의 선언, 일반 전송 및 RPC
6
+ 요청에 우선순위를 연결했다. 기존 큐 종류를 변경하거나 Redis 기능을 추가하지 않았다.
7
+
8
+ ## 2. 주요 변경
9
+
10
+ - [Subscription](../../../src/Subscription.py)의 기존 필드 뒤에 `max_priority`와
11
+ `priority`를 추가하여 기존 위치 인자의 의미를 보존했다. 매핑 입력에도 동일하게 적용한다.
12
+ - 최대값은 `1..255` 정수로 검증한다. 메시지 값은 최대값이 있으면 그 범위,
13
+ 없으면 `0..255`로 검증한다. `bool`은 거부한다.
14
+ - 구독 생성·파싱 시 `priority`가 있고 최대값이 없으면 `max_priority=255`로
15
+ 정규화하고 두 값의 범위를 함께 검증한다. 최대값만 있으면 기본값은 `None`이다.
16
+ - [Connection](../../../src/Connection.py)의 `priority_parameters`는
17
+ `Subscription.parse`에 위임한다. 기본값을 별도 인자로 받거나 `Run`에서 가져오지 않는다.
18
+ - `declare`는 최대값을 `x-max-priority`에 반영하고 기존 DLQ 인자를 보존한다.
19
+ 오류 큐에는 작업 우선순위를 자동 적용하지 않는다. 선언 충돌은 그대로 전파한다.
20
+ - `send`는 메시지 우선순위를 AMQP 속성에 반영한다.
21
+ `request`는 `Queue.call`까지 우선순위를 전달한다. 두 메서드는 호출값이
22
+ `None`이면 `Subscription.priority`를 사용하며 명시적 `0`을 보존한다.
23
+ - [Queue.call](../../../src/Queue.py)은 기존 타임아웃 위치 인자를 보존하면서
24
+ 키워드 전용 `priority`를 추가했다. 임시 응답 큐와 응답 메시지에는 적용하지 않는다.
25
+ - 기존 `Queue.send`, [Exchange.send](../../../src/Exchange.py)도 공유 검증 함수를
26
+ 사용하여 브로커 범위를 검증한다. 작업 정의를 받지 않는 저수준 API는 작업별
27
+ 최대값을 강제하지 않는다.
28
+ - 기존 publisher confirm, mandatory, RPC correlation ID, 만료, 원격 오류,
29
+ 연결 정리 및 브로커 NACK 재전달 방식은 유지했다.
30
+ - 공개 바인딩 클래스 `Any`와 충돌하지 않도록 타입 주석용 `typing.Any`는
31
+ `AnyValue`로 임포트했다. 공개 바인딩 클래스 이름과 런타임 동작은 유지했다.
32
+
33
+ ## 3. 검증 결과
34
+
35
+ - 전체 어댑터 테스트 35개 중 32개 통과, 선택적 통합 테스트 3개 건너뜀.
36
+ - 새 단위 테스트 5개로 구독 파싱과 범위, DLQ 인자 병합, 선언 오류 전파,
37
+ 큐·exchange 전송 및 실제 RPC 발행 속성을 모의 연결로 검증했다.
38
+ - 실제 classic 큐의 우선순위·동순위 FIFO 전달, 재전달 속성 보존 및 선언 충돌을
39
+ 검증하는 선택적 통합 테스트 1개를 추가했다.
40
+ - 로컬 AMQP 포트에는 접속할 수 있었지만 기본 계정은 인증 거부를 받았다.
41
+ 사용자가 제공한 별도 계정으로 재시도한 결과 인증 후 `/` vhost 접근이
42
+ `530 NOT_ALLOWED`로 거부되어 실제 전달 검증은 완료하지 못했다.
43
+ - 서버의 계정, 권한, vhost 및 기존 큐는 변경하지 않았다.
44
+ 인증 정보는 소스나 문서에 저장하지 않고 해당 테스트 프로세스의 환경변수로만 전달했다.
45
+ - 후속 검증에서는 작업 폴더의 Docker 설정으로 RabbitMQ 3.13.7을 새로 기동했다.
46
+ 기본 `guest` 계정과 `/` vhost, classic 큐에서 전체 35개 테스트가
47
+ 건너뜀 없이 통과했다. 앞선 인증·권한 문제로 인한 통합 검증 제한은 해소됐다.
48
+ - 높은 우선순위 우선 전달, 동순위 FIFO, 재전달 속성 보존, 선언 충돌 전파와
49
+ 기존 워커·RPC 성공·실패·타임아웃 경로를 실제 브로커에서 검증했다.
50
+ - 어댑터 원본 경로의 Pylance 진단은 오류가 없었지만, 코어 내부 정션 경로와
51
+ 테스트의 import 분석에는 이전 시그니처 및 타입 진단이 남아 있었다.
52
+ 실제 파일·런타임 시그니처와 분석 결과가 달라 전체 정적 타입 검증을
53
+ 통과한 것으로 간주하지 않는다. 큐의 `auto_decode`, 기존 nullable 주석 등
54
+ 이번 기능과 무관한 진단도 있어 일괄 수정하지 않았다.
55
+ - 기본값을 `Subscription.priority`로 통합한 뒤 Docker RabbitMQ 3.13.7에서
56
+ 전체 38개 테스트가 건너뜀 없이 통과했다. 구독 기본값의 정규화·입력 불변성,
57
+ 잘못된 기본값과 최대값 조합, 직접 브로커 전송·RPC의 기본값 사용을 추가로 검증했다.
58
+ - 최신 Pylance 진단에서는 어댑터 원본 경로의 상대 import 해석 문제와 기존
59
+ 타입 진단도 확인되어 전체 정적 분석 통과 상태는 아니다.
60
+
61
+ ## 4. 재검증 방법
62
+
63
+ 작업 폴더 루트에서 단위 테스트를 실행한다.
64
+
65
+ ```powershell
66
+ $env:PYTHONIOENCODING = 'utf-8'
67
+ $env:PYTHONUTF8 = '1'
68
+ .\.venv\Scripts\python.exe -m unittest discover -s .\DefStream-RabbitMQ\test
69
+ ```
70
+
71
+ 실제 브로커 테스트는 로컬 `127.0.0.1:5672`의 `/` vhost에 큐 생성·발행·소비·삭제
72
+ 권한이 있는 계정이 필요하다. `DEFSTREAM_RABBITMQ_USERNAME`과
73
+ `DEFSTREAM_RABBITMQ_PASSWORD` 환경변수에 테스트 인증 정보를 설정한 후 실행한다.
74
+ 미설정 시 기존 테스트 관례대로 `guest`를 사용한다.
75
+
76
+ ```powershell
77
+ $env:DEFSTREAM_RABBITMQ_TESTS = '1'
78
+ .\.venv\Scripts\python.exe -m unittest discover -s .\DefStream-RabbitMQ\test -p test_priorities.py
79
+ ```
80
+
81
+ 통합 테스트는 고유한 이름의 큐만 생성하고 종료 시 해당 큐를 삭제한다.
82
+
83
+ ## 5. 공통 브로커 참조 경로 통일
84
+
85
+ - 모든 공통 인터페이스·예외·검증 함수·정책 참조를
86
+ `from DefStream.Broker import ...`로 변경했다.
87
+ - 구현체 내부 상대 import와 기존 공개 정책 재노출은 유지했다.
88
+ - RabbitMQ 3.13.7 실서버 통합 테스트를 포함한 전체 38개 테스트가 통과했다.
89
+
90
+ ## 6. 관련 문서
91
+
92
+ - [사용 설명서](../../../README.md)
93
+ - [공통 워커 구현 기록](../../../../DefStream/issues/feature/1/IMPLEMENTS.md)
@@ -0,0 +1,103 @@
1
+ # RabbitMQ 워커 작업 우선순위 지원
2
+
3
+ ## 변경 전 (AS-IS)
4
+
5
+ 저수준 `create_queue(maxPriority=...)`, `Queue.send(priority=...)`에서는
6
+ 우선순위를 지정할 수 있었지만, 워커의 작업 정의와 `declare`, `send`, `request`
7
+ 경로에서는 이를 설정할 수 없었습니다. `Queue.call`도 요청 우선순위를 받지 않았습니다.
8
+
9
+ ## 변경 후 (TO-BE)
10
+
11
+ 작업에 지정하는 `Subscription`에서 큐 최대 우선순위와 기본 메시지 우선순위를
12
+ 함께 정의할 수 있습니다. `declare`가 이 설정으로 classic 우선순위 큐를 생성하고,
13
+ `send`와 `request`는 작업 기본값 또는 호출별 우선순위로 요청을 발행합니다.
14
+ 저수준 `Queue.call`에도 `priority=`를 사용할 수 있습니다.
15
+
16
+ ## 사용 방법
17
+
18
+ 작업 정의의 구독 설정에 `max_priority`와 `priority`를 추가하세요.
19
+
20
+ ```python
21
+ from DefStream.Broker.Implements.RabbitMQ import Subscription
22
+ from DefStream.Worker import (
23
+ Run,
24
+ declare,
25
+ request,
26
+ send,
27
+ )
28
+
29
+
30
+ class Add(
31
+ Run,
32
+ parameters=Subscription(
33
+ queue='tasks.add.priority',
34
+ qos=1,
35
+ max_priority=5,
36
+ priority=2,
37
+ ),
38
+ ):
39
+ def run(
40
+ self,
41
+ a,
42
+ b,
43
+ ):
44
+ return a + b
45
+ ```
46
+
47
+ 아래 코드는 호출자가 연결한 RabbitMQ `Connection`인 `broker`를 사용합니다.
48
+ 생산자와 소비자가 같은 작업 정의를 사용하고, 전송 전에 큐를 준비하세요.
49
+
50
+ ```python
51
+ declare(
52
+ task=Add,
53
+ broker=broker,
54
+ )
55
+
56
+ # 작업 기본 우선순위 2로 전송합니다.
57
+ send(
58
+ {'a': 1, 'b': 2},
59
+ task=Add,
60
+ broker=broker,
61
+ )
62
+
63
+ # 실행 중인 Worker에 우선순위 5로 RPC 요청을 보냅니다.
64
+ result = request(
65
+ {'a': 1, 'b': 2},
66
+ task=Add,
67
+ broker=broker,
68
+ priority=5,
69
+ )
70
+ ```
71
+
72
+ | 설정 | 적용 결과 |
73
+ |---|---|
74
+ | `priority`만 지정 | 큐 최대값은 `255` |
75
+ | `max_priority`만 지정 | 기본 메시지 우선순위는 `None` |
76
+ | 호출값 생략 또는 `None` | 구독의 기본 `priority` 사용 |
77
+ | 호출값 `0` | 기본값을 덮어쓰는 가장 낮은 우선순위 |
78
+ | 작업·호출 모두 미지정 | 기존 동작 유지 |
79
+
80
+ ## 기존 코드에 미치는 영향과 전환
81
+
82
+ - 우선순위를 사용하지 않는 기존 작업은 수정할 필요가 없습니다.
83
+ - 기존 일반 큐에 우선순위를 추가하거나 큐 최대값을 변경하면 재선언이 실패합니다.
84
+ 새 큐 이름으로 선언하고 생산자·소비자를 함께 전환하세요. 같은 이름을 유지해야
85
+ 한다면 미처리 메시지를 보존한 뒤 운영 절차에 따라 큐를 재생성해야 합니다.
86
+ 라이브러리는 기존 큐를 자동 삭제하지 않습니다.
87
+ - 우선순위는 정수여야 하며 `bool`은 허용하지 않습니다. 최대값은 `1..255`,
88
+ 메시지 값은 `0..큐 최대값`이어야 합니다. 타입 오류는 `TypeError`,
89
+ 범위 오류는 `ValueError`가 발생합니다.
90
+ - 작업 설정 없이 호출에서만 지정하면 `0..255` 범위만 검증합니다.
91
+ 이 경우 우선순위 큐의 사전 선언과 실제 큐 최대값 준수는 호출자 책임입니다.
92
+ - 기존 저수준 `Queue.send`, `Exchange.send`도 이제 우선순위의 타입과
93
+ `0..255` 범위를 명시적으로 검증합니다. 문자열·실수 등의 입력은 정수로 수정하세요.
94
+ - 자식 작업에서 구독 설정을 교체할 때 부모의 기본·최대 우선순위를 제거할 수
95
+ 없습니다. 유지할 값이나 재정의할 유효한 값을 새 `Subscription`에도 명시하세요.
96
+
97
+ ## 전달 순서 주의사항
98
+
99
+ 숫자가 클수록 같은 큐에 대기 중인 메시지를 먼저 전달합니다. 이미 prefetch되거나
100
+ 실행 중인 작업은 선점하지 않으며, 여러 소비자의 실행·완료 순서는 보장하지 않습니다.
101
+ 높은 우선순위가 계속 들어오면 낮은 우선순위가 오래 대기할 수 있습니다.
102
+ 큐 최대값이 클수록 자원 비용이 증가하므로 필요한 수준만 지정하세요.
103
+ 이번 기능은 classic 큐를 대상으로 하며 Stream·Quorum 큐는 대상이 아닙니다.