DefStream 0.1.11__tar.gz → 0.1.13__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 (117) hide show
  1. defstream-0.1.13/.github/ISSUE_TEMPLATE/bug_report.yml +61 -0
  2. defstream-0.1.13/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. defstream-0.1.13/.github/ISSUE_TEMPLATE/feature_request.yml +36 -0
  4. defstream-0.1.13/.github/PULL_REQUEST_TEMPLATE/hotfix.md +34 -0
  5. defstream-0.1.13/.github/PULL_REQUEST_TEMPLATE.md +24 -0
  6. {defstream-0.1.11 → defstream-0.1.13}/.gitignore +2 -4
  7. {defstream-0.1.11 → defstream-0.1.13}/DefStream.egg-info/PKG-INFO +1 -1
  8. {defstream-0.1.11 → defstream-0.1.13}/DefStream.egg-info/SOURCES.txt +10 -0
  9. {defstream-0.1.11 → defstream-0.1.13}/PKG-INFO +1 -1
  10. defstream-0.1.13/issues/bug/16/IMPLEMENTS.md +32 -0
  11. defstream-0.1.13/issues/bug/16/ISSUE.md +19 -0
  12. defstream-0.1.13/issues/bug/16/README.md +12 -0
  13. defstream-0.1.13/issues/bug/16/REQUIREMENTS.md +25 -0
  14. defstream-0.1.13/issues/fix/13/README.md +31 -0
  15. {defstream-0.1.11 → defstream-0.1.13}/sample/Worker/RabbitMQ/Client.py +2 -2
  16. {defstream-0.1.11 → defstream-0.1.13}/sample/Worker/RabbitMQ/Tasks.py +23 -1
  17. {defstream-0.1.11 → defstream-0.1.13}/sample/Worker/RabbitMQ/Worker.py +7 -4
  18. {defstream-0.1.11 → defstream-0.1.13}/sample/Worker/Redis/Sample.py +22 -15
  19. {defstream-0.1.11 → defstream-0.1.13}/sample/Worker/Sample.py +25 -16
  20. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/Console.py +4 -3
  21. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Startup.py +42 -3
  22. {defstream-0.1.11 → defstream-0.1.13}/test/test_inspector_console.py +56 -2
  23. {defstream-0.1.11 → defstream-0.1.13}/test/test_logging.py +1 -1
  24. {defstream-0.1.11 → defstream-0.1.13}/.github/workflows/ci.yml +0 -0
  25. {defstream-0.1.11 → defstream-0.1.13}/DefStream.egg-info/dependency_links.txt +0 -0
  26. {defstream-0.1.11 → defstream-0.1.13}/DefStream.egg-info/requires.txt +0 -0
  27. {defstream-0.1.11 → defstream-0.1.13}/DefStream.egg-info/top_level.txt +0 -0
  28. {defstream-0.1.11 → defstream-0.1.13}/LICENSE +0 -0
  29. {defstream-0.1.11 → defstream-0.1.13}/README.en.md +0 -0
  30. {defstream-0.1.11 → defstream-0.1.13}/README.md +0 -0
  31. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/1/IMPLEMENTS.md +0 -0
  32. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/1/README.md +0 -0
  33. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/1/REQUIREMENTS.md +0 -0
  34. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/1/TASK.md +0 -0
  35. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/1/ci.refs.json +0 -0
  36. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/8/IMPLEMENTS.md +0 -0
  37. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/8/README.md +0 -0
  38. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/8/REQUIREMENTS.md +0 -0
  39. {defstream-0.1.11 → defstream-0.1.13}/issues/feature/8/ci.refs.json +0 -0
  40. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/10/IMPLEMENTS.md +0 -0
  41. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/10/README.md +0 -0
  42. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/10/REQUIREMENTS.md +0 -0
  43. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/3/IMPLEMENTS.md +0 -0
  44. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/3/README.md +0 -0
  45. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/3/REQUIREMENTS.md +0 -0
  46. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/3/ci.refs.json +0 -0
  47. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/6/IMPLEMENTS.md +0 -0
  48. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/6/README.md +0 -0
  49. {defstream-0.1.11 → defstream-0.1.13}/issues/fix/6/REQUIREMENTS.md +0 -0
  50. {defstream-0.1.11 → defstream-0.1.13}/sample/Broker/Sample.py +0 -0
  51. {defstream-0.1.11 → defstream-0.1.13}/setup.cfg +0 -0
  52. {defstream-0.1.11 → defstream-0.1.13}/setup.py +0 -0
  53. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Configuration.py +0 -0
  54. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Connection.py +0 -0
  55. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Consumer.py +0 -0
  56. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Errors.py +0 -0
  57. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Event.py +0 -0
  58. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Implements/__init__.py +0 -0
  59. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Policies.py +0 -0
  60. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Priority.py +0 -0
  61. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/Protocol.py +0 -0
  62. {defstream-0.1.11 → defstream-0.1.13}/src/Broker/__init__.py +0 -0
  63. {defstream-0.1.11 → defstream-0.1.13}/src/Descriptor/DataObject.py +0 -0
  64. {defstream-0.1.11 → defstream-0.1.13}/src/Descriptor/Types.py +0 -0
  65. {defstream-0.1.11 → defstream-0.1.13}/src/Descriptor/Value.py +0 -0
  66. {defstream-0.1.11 → defstream-0.1.13}/src/Descriptor/__init__.py +0 -0
  67. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extension.py +0 -0
  68. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/Console.html +0 -0
  69. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/Dashboard.html +0 -0
  70. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/Dashboard.py +0 -0
  71. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/HealthCheck.py +0 -0
  72. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/Metrics.py +0 -0
  73. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/Specifications.py +0 -0
  74. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Extensions/__init__.py +0 -0
  75. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Inspector.py +0 -0
  76. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/Response.py +0 -0
  77. {defstream-0.1.11 → defstream-0.1.13}/src/Inspector/__init__.py +0 -0
  78. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Pattern.py +0 -0
  79. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Array.py +0 -0
  80. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Common.py +0 -0
  81. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Compare.py +0 -0
  82. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Condition.py +0 -0
  83. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/DataObject.py +0 -0
  84. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/DateTime.py +0 -0
  85. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Number.py +0 -0
  86. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Object.py +0 -0
  87. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Size.py +0 -0
  88. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/String.py +0 -0
  89. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/Type.py +0 -0
  90. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Patterns/__init__.py +0 -0
  91. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Validate.py +0 -0
  92. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/Validator.py +0 -0
  93. {defstream-0.1.11 → defstream-0.1.13}/src/Validator/__init__.py +0 -0
  94. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Executor.py +0 -0
  95. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Message.py +0 -0
  96. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Pool.py +0 -0
  97. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Run.py +0 -0
  98. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Specification.py +0 -0
  99. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/Worker.py +0 -0
  100. {defstream-0.1.11 → defstream-0.1.13}/src/Worker/__init__.py +0 -0
  101. {defstream-0.1.11 → defstream-0.1.13}/src/__init__.py +0 -0
  102. {defstream-0.1.11 → defstream-0.1.13}/test/test_dashboard.py +0 -0
  103. {defstream-0.1.11 → defstream-0.1.13}/test/test_dashboard_ui.cjs +0 -0
  104. {defstream-0.1.11 → defstream-0.1.13}/test/test_descriptor.py +0 -0
  105. {defstream-0.1.11 → defstream-0.1.13}/test/test_inspector.py +0 -0
  106. {defstream-0.1.11 → defstream-0.1.13}/test/test_inspector_logging.py +0 -0
  107. {defstream-0.1.11 → defstream-0.1.13}/test/test_lifecycle_hooks.py +0 -0
  108. {defstream-0.1.11 → defstream-0.1.13}/test/test_pool_callbacks.py +0 -0
  109. {defstream-0.1.11 → defstream-0.1.13}/test/test_process_signals.py +0 -0
  110. {defstream-0.1.11 → defstream-0.1.13}/test/test_protocol_contracts.py +0 -0
  111. {defstream-0.1.11 → defstream-0.1.13}/test/test_release_workflow.py +0 -0
  112. {defstream-0.1.11 → defstream-0.1.13}/test/test_runner_hooks.py +0 -0
  113. {defstream-0.1.11 → defstream-0.1.13}/test/test_specifications.py +0 -0
  114. {defstream-0.1.11 → defstream-0.1.13}/test/test_task_messages.py +0 -0
  115. {defstream-0.1.11 → defstream-0.1.13}/test/test_task_priorities.py +0 -0
  116. {defstream-0.1.11 → defstream-0.1.13}/test/test_worker.py +0 -0
  117. {defstream-0.1.11 → defstream-0.1.13}/test/test_worker_startup.py +0 -0
@@ -0,0 +1,61 @@
1
+ name: 버그/오류 제보
2
+ description: 예상과 다르게 동작하거나 오류가 발생하는 문제를 제보합니다.
3
+ title: "[버그] "
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >
8
+ 재현 가능한 버그를 제보해 주세요. 로그나 설정을 첨부할 때 토큰, 비밀번호,
9
+ 개인정보 등 민감한 정보는 반드시 삭제해 주세요.
10
+ - type: input
11
+ id: version
12
+ attributes:
13
+ label: 버전
14
+ description: 사용 중인 버전 또는 커밋을 입력해 주세요.
15
+ placeholder: 예) 0.1.12
16
+ validations:
17
+ required: true
18
+ - type: textarea
19
+ id: expected
20
+ attributes:
21
+ label: 기대한 동작
22
+ description: 어떤 결과를 기대했는지 설명해 주세요.
23
+ validations:
24
+ required: true
25
+ - type: textarea
26
+ id: actual
27
+ attributes:
28
+ label: 실제 동작
29
+ description: 실제로 발생한 결과를 설명해 주세요. 오류가 표시되면 메시지도 포함해 주세요.
30
+ validations:
31
+ required: true
32
+ - type: textarea
33
+ id: reproduction
34
+ attributes:
35
+ label: 재현 방법
36
+ description: 문제를 재현하는 단계나 최소 코드 예제를 작성해 주세요.
37
+ placeholder: |
38
+ 1. 다음 설정으로 Worker를 시작합니다.
39
+ 2. 특정 메시지를 전달합니다.
40
+ 3. 오류가 발생합니다.
41
+ validations:
42
+ required: true
43
+ - type: textarea
44
+ id: environment
45
+ attributes:
46
+ label: 실행 환경
47
+ description: Python 버전, 운영체제, 브로커 및 관련 어댑터 버전을 입력해 주세요.
48
+ placeholder: |
49
+ - Python:
50
+ - 운영체제:
51
+ - 브로커:
52
+ - 어댑터:
53
+ validations:
54
+ required: true
55
+ - type: textarea
56
+ id: additional
57
+ attributes:
58
+ label: 추가 정보
59
+ description: 관련 로그나 스크린샷 등 문제 파악에 도움이 되는 정보를 적어 주세요.
60
+ validations:
61
+ required: false
@@ -0,0 +1 @@
1
+ blank_issues_enabled: false
@@ -0,0 +1,36 @@
1
+ name: 기능 제안
2
+ description: DefStream에 추가하거나 개선할 기능을 제안합니다.
3
+ title: "[기능 제안] "
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >
8
+ 해결하려는 문제와 제안하는 사용 경험을 중심으로 작성해 주세요.
9
+ - type: textarea
10
+ id: problem
11
+ attributes:
12
+ label: 해결하려는 문제
13
+ description: 현재 어떤 불편이나 제약이 있는지 설명해 주세요.
14
+ validations:
15
+ required: true
16
+ - type: textarea
17
+ id: proposal
18
+ attributes:
19
+ label: 제안하는 해결 방법
20
+ description: 원하는 기능과 기대하는 동작을 설명해 주세요.
21
+ validations:
22
+ required: true
23
+ - type: textarea
24
+ id: alternatives
25
+ attributes:
26
+ label: 고려한 대안
27
+ description: 고려한 다른 방법이나 우회 방법이 있다면 적어 주세요.
28
+ validations:
29
+ required: false
30
+ - type: textarea
31
+ id: additional
32
+ attributes:
33
+ label: 추가 정보
34
+ description: 예제, 참고 자료 등 제안의 맥락을 이해하는 데 도움이 되는 정보를 적어 주세요.
35
+ validations:
36
+ required: false
@@ -0,0 +1,34 @@
1
+ ## 핫픽스 요약
2
+
3
+ <!-- 긴급 수정이 필요한 이유와 해결 내용을 요약해 주세요. -->
4
+
5
+ ## 관련 이슈와 영향 범위
6
+
7
+ - 관련 이슈:
8
+ - 영향을 받는 버전:
9
+ - 영향받는 사용자·기능:
10
+ - 심각도 및 긴급 배포가 필요한 이유:
11
+
12
+ ## 원인과 수정 내용
13
+
14
+ <!-- 문제의 원인, 수정 방법, 변경 범위를 작성해 주세요. -->
15
+
16
+ ## 회귀 방지 및 검증
17
+
18
+ <!-- 재현 테스트와 실행한 검증 결과를 작성해 주세요. 실행하지 못한 검증은 사유를 적어 주세요. -->
19
+
20
+ - [ ] 문제를 재현하는 회귀 테스트를 추가하거나 기존 테스트로 수정 동작을 확인했습니다.
21
+ - [ ] 관련 테스트를 실행했습니다.
22
+ - [ ] 호환성 및 영향 범위를 확인했습니다.
23
+
24
+ ## 배포·복구 계획
25
+
26
+ - 배포 시 주의사항:
27
+ - 롤백 또는 복구 방법:
28
+ - 후속 수정이 필요한 경우 관련 이슈:
29
+
30
+ ## 제출 전 확인
31
+
32
+ - [ ] 수정 범위가 긴급 문제 해결에 필요한 변경으로 한정되어 있습니다.
33
+ - [ ] 오류 로그와 변경 내용을 민감 정보 없이 확인했습니다.
34
+ - [ ] 검증하지 않은 항목을 완료한 것으로 표시하지 않았습니다.
@@ -0,0 +1,24 @@
1
+ ## 변경 내용
2
+
3
+ <!-- 무엇을 왜 변경했는지 간단히 요약해 주세요. -->
4
+
5
+ ## 변경 유형
6
+
7
+ - [ ] 버그 수정
8
+ - [ ] 기능 추가
9
+ - [ ] 호환성에 영향을 주는 변경
10
+ - [ ] 문서 또는 예제 변경
11
+ - [ ] 기타
12
+
13
+ ## 검증
14
+
15
+ <!-- 실행한 테스트와 결과를 작성해 주세요. 테스트를 실행하지 않았다면 사유를 적어 주세요. -->
16
+
17
+ - [ ] 관련 테스트를 실행했습니다.
18
+ - [ ] 문서와 예제를 확인했습니다.
19
+
20
+ ## 확인 사항
21
+
22
+ - [ ] 변경 범위가 요청 사항에 한정되어 있습니다.
23
+ - [ ] 공개 API나 기존 동작에 영향을 주는 경우 변경 내용과 마이그레이션 방법을 설명했습니다.
24
+ - [ ] 민감 정보나 불필요한 디버깅 코드를 포함하지 않았습니다.
@@ -53,10 +53,8 @@ tmp/
53
53
  .git/
54
54
 
55
55
  # GitHub specific files
56
- .github/
57
- !/.github/
58
- /.github/*
59
- !/.github/workflows/
56
+ .github/instructions
57
+ .github/instructions/
60
58
 
61
59
  # GitLab specific files
62
60
  .gitlab/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
@@ -3,12 +3,21 @@ LICENSE
3
3
  README.en.md
4
4
  README.md
5
5
  setup.py
6
+ .github/PULL_REQUEST_TEMPLATE.md
7
+ .github/ISSUE_TEMPLATE/bug_report.yml
8
+ .github/ISSUE_TEMPLATE/config.yml
9
+ .github/ISSUE_TEMPLATE/feature_request.yml
10
+ .github/PULL_REQUEST_TEMPLATE/hotfix.md
6
11
  .github/workflows/ci.yml
7
12
  DefStream.egg-info/PKG-INFO
8
13
  DefStream.egg-info/SOURCES.txt
9
14
  DefStream.egg-info/dependency_links.txt
10
15
  DefStream.egg-info/requires.txt
11
16
  DefStream.egg-info/top_level.txt
17
+ issues/bug/16/IMPLEMENTS.md
18
+ issues/bug/16/ISSUE.md
19
+ issues/bug/16/README.md
20
+ issues/bug/16/REQUIREMENTS.md
12
21
  issues/feature/1/IMPLEMENTS.md
13
22
  issues/feature/1/README.md
14
23
  issues/feature/1/REQUIREMENTS.md
@@ -21,6 +30,7 @@ issues/feature/8/ci.refs.json
21
30
  issues/fix/10/IMPLEMENTS.md
22
31
  issues/fix/10/README.md
23
32
  issues/fix/10/REQUIREMENTS.md
33
+ issues/fix/13/README.md
24
34
  issues/fix/3/IMPLEMENTS.md
25
35
  issues/fix/3/README.md
26
36
  issues/fix/3/REQUIREMENTS.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
@@ -0,0 +1,32 @@
1
+ # Inspector Console 사용자 지정 Task 이름 버그 수정 기록
2
+
3
+ ## 구현 개요
4
+
5
+ [요구사항 명세서](./REQUIREMENTS.md)에 따라 Inspector Console이 작업 목록과
6
+ 동일한 작업 ID를 사용해 등록된 Task를 찾고, 잘못된 요청의 구체적인 원인을
7
+ 경고 로그에서 확인할 수 있도록 수정했다.
8
+
9
+ ## 주요 변경
10
+
11
+ - `Console.execute()`의 Task 검색과 중복 검사를 모두 `task.__name__`에서
12
+ `task.name` 비교로 변경했다. 작업 이름을 사용자 지정한 경우에도
13
+ Specifications 문서와 Console UI가 전달하는 ID로 올바른 Task를 식별한다.
14
+ - 잘못된 입력을 거부하는 경고 로그에 예외 종류와 예외 메시지를 함께 기록한다.
15
+ - `test_inspector_console.py`에 사용자 지정 ID의 발행 성공, 중복 ID 거부,
16
+ 미등록 ID의 오류 로그 내용을 확인하는 회귀 테스트를 추가했다.
17
+ - RabbitMQ·Redis 실서버 테스트의 요청 ID도 실제 등록된 하위 Task의 `Live.name`을
18
+ 사용하도록 했다. 클래스의 `__name__`(`Live`)와 Worker에 등록하는 유효 작업
19
+ 이름은 같다고 가정할 수 없으며, 이전의 하드코딩은 요청을 400으로 거부하게 했다.
20
+
21
+ ## 검증
22
+
23
+ DefStream 테스트 디렉터리에서 Python 3.12.10으로 다음 명령을 실행했다.
24
+
25
+ ```powershell
26
+ $env:PYTHONIOENCODING = 'utf-8'
27
+ ..\..\.venv\Scripts\python.exe -m unittest test_inspector_console test_inspector_logging
28
+ ```
29
+
30
+ Inspector Console 및 로깅 관련 테스트 28개가 통과했고, 선택 통합 테스트 2개는
31
+ 건너뛰었다. 변경된 두 Python 파일의 `compile()` 문법 검사와
32
+ `git diff --check`도 통과했다. 브로커 실서버 통합 검증은 수행하지 않았다.
@@ -0,0 +1,19 @@
1
+ # Inspector Console에서 사용자 지정 Task 이름으로 요청 시 큐 발행 전에 거부됨
2
+
3
+ ## 버전
4
+
5
+ 0.1.12
6
+
7
+ ## 내용
8
+
9
+ Inspector Console이 Task 목록에 표시하는 이름과 실제 Task를 찾는 기준이 다릅니다.
10
+
11
+ Specifications.document()은 task.name을 작업 ID로 사용합니다. Console UI는 이 ID를 선택해 요청을 보내지만, Console.execute()는 등록된 작업을 찾을 때 task.__name__과 요청의 task 값을 비교합니다.
12
+
13
+ 예를 들어 Orbis Split은 Split.name이 ETL.OBS.Split이고 Split.__name__은 Split입니다. 따라서 UI에서 선택한 작업 ID ETL.OBS.Split과 Console의 비교 값 Split이 일치하지 않아 Unknown registered task 오류가 발생합니다. 이 검사는 브로커에 연결하거나 메시지를 발행하기 전에 수행되므로 요청은 큐에 도달하지 않습니다.
14
+
15
+ 또한 Console 경고 로그에는 구체적인 오류 메시지 대신 예외 종류만 기록되어 원인을 파악하기 어렵습니다.
16
+
17
+ 기대 동작: Console이 Task 목록을 만들 때와 동일하게 task.name을 기준으로 작업을 찾아야 합니다. 중복 여부 검사도 같은 기준을 사용해야 합니다.
18
+
19
+ 실제 동작: Console이 task.__name__을 기준으로 작업을 찾아 사용자 지정 이름을 사용하는 Task를 거부합니다.
@@ -0,0 +1,12 @@
1
+ # Inspector Console 사용자 지정 Task 이름 처리
2
+
3
+ Inspector Console이 작업 목록에 표시한 ID로 Task를 찾도록 수정했습니다.
4
+ 따라서 `ETL.OBS.Split`처럼 클래스 이름과 다른 사용자 지정 Task 이름으로도
5
+ 요청을 보낼 수 있습니다. 동일한 작업 ID가 중복 등록된 경우에는 모호한 작업
6
+ 실행을 막기 위해 요청을 거부합니다.
7
+
8
+ 잘못된 요청은 기존처럼 거부되며, 경고 로그에 예외 종류와 구체적인 원인이 함께
9
+ 기록됩니다.
10
+
11
+ 세부 내용은 [요구사항 명세서](./REQUIREMENTS.md)와
12
+ [구현 및 검증 기록](./IMPLEMENTS.md)을 참고하세요.
@@ -0,0 +1,25 @@
1
+ # Inspector Console의 사용자 지정 Task 이름 처리 요구사항
2
+
3
+ ## 목적
4
+
5
+ Inspector Console이 Task 목록에 표시한 작업 ID로 등록된 Task를 찾아 실행하도록 한다. Task에 사용자 지정 이름이 지정되어 있어도 요청이 잘못 거부되거나 큐 발행 전에 중단되지 않아야 한다.
6
+
7
+ ## 배경
8
+
9
+ `Specifications.document()`은 `task.name`을 작업 ID로 사용하고, Console UI는 이 ID를 요청에 포함한다. 그러나 `Console.execute()`는 등록된 작업을 찾을 때 `task.__name__`과 요청의 작업 ID를 비교한다.
10
+
11
+ 예를 들어 `Split.name`이 `ETL.OBS.Split`이고 `Split.__name__`이 `Split`이면, 요청 ID `ETL.OBS.Split`과 비교 값이 달라 `Unknown registered task` 오류가 발생한다. 이 오류는 브로커 연결 및 메시지 발행 전에 발생하므로 요청이 큐에 도달하지 않는다.
12
+
13
+ ## 요구사항
14
+
15
+ 1. Console은 요청의 작업 ID와 등록된 Task의 `task.name`을 비교해 실행할 Task를 찾아야 한다.
16
+ 2. 중복 작업 ID 검사도 `task.name`을 기준으로 수행해야 한다. 같은 이름을 가진 등록 Task가 중복으로 허용되어서는 안 된다.
17
+ 3. 목록에 표시된 사용자 지정 작업 ID로 요청하면, 해당 Task를 찾지 못했다는 오류로 거부하지 않고 정상적인 실행 절차를 진행해야 한다.
18
+ 4. 요청 처리 중 오류가 발생하면 경고 로그에 예외 종류뿐 아니라 구체적인 오류 메시지도 기록해야 한다.
19
+
20
+ ## 완료 기준
21
+
22
+ - `task.name`과 `task.__name__`이 다른 Task를 목록의 작업 ID로 요청해도 올바른 Task가 선택된다.
23
+ - 중복 검사와 Task 검색이 모두 `task.name`을 기준으로 일관되게 동작한다.
24
+ - 유효한 사용자 지정 작업 ID 요청은 큐 발행 단계까지 진행되며, 해당 요청이 `Unknown registered task` 오류로 중단되지 않는다.
25
+ - 오류 로그에서 예외 종류와 구체적인 원인을 확인할 수 있다.
@@ -0,0 +1,31 @@
1
+ # Worker 시작 배너와 샘플 로깅 순서
2
+
3
+ ## 변경 요약
4
+
5
+ Worker 시작 배너를 로깅 설정 여부와 관계없이 먼저 확인할 수 있도록 Worker
6
+ 샘플의 로깅 설정 시점을 `WorkerSetup` 훅으로 옮겼습니다. 시작 배너는 터미널에서
7
+ 청록·파랑 계열로 강조하며, ASCII 아트의 모양과 문구는 유지합니다.
8
+
9
+ ## 변경 내용
10
+
11
+ - [Worker 샘플](../../../sample/Worker/Sample.py)은 `configure_logging()`을
12
+ 실행 진입점에서 호출하는 대신 `SetupLogger` setup 훅에서 수행합니다.
13
+ Worker 생성·작업 등록 시점의 로그는 아직 설정되지 않은 핸들러에 의해 출력되지
14
+ 않으며, 시작 배너가 샘플의 첫 화면 출력이 됩니다.
15
+ - [Startup](../../../src/Worker/Startup.py)은 stdout이 터미널이고 `NO_COLOR`
16
+ 환경 변수가 설정되지 않은 경우에만 로고에 색을 적용합니다. `Starting Worker`
17
+ 제목도 굵은 청록색으로 표시합니다. 파일·파이프 등으로 출력을 리다이렉트하거나
18
+ `NO_COLOR`를 설정하면 ANSI 색상 코드를 출력하지 않습니다.
19
+ - `Worker.run()`의 로그와 배너 호출 순서는 변경하지 않았습니다. 따라서
20
+ 로깅 핸들러가 미리 설정된 애플리케이션에서는 `Starting Worker` 로그가 배너보다
21
+ 먼저 기록될 수 있습니다. 이 변경은 샘플의 로깅 설정 시점을 조정해 샘플 출력에서
22
+ 배너를 먼저 보이게 합니다.
23
+
24
+ ## 호환성 및 검증 범위
25
+
26
+ 시작 배너의 ASCII 문자와 구성 출력은 유지됩니다. 색상은 터미널 조건에서만
27
+ 추가되며 `NO_COLOR`를 통해 끌 수 있습니다.
28
+
29
+ 로고 색상 적용 후 ANSI 코드를 제거한 결과가 원본과 동일한지, 수정한 Python 소스의
30
+ 문법이 유효한지 확인했습니다. Worker 시작 테스트는 환경에 `pika`가 없어 실행하지
31
+ 못했으며, 샘플의 로깅 순서 변경은 별도 실행으로 검증하지 않았습니다.
@@ -3,7 +3,7 @@ from random import randint
3
3
  from Tasks import (
4
4
  Add,
5
5
  Divide,
6
- LoggingSetup,
6
+ TaskLoggingSetup,
7
7
  logger,
8
8
  )
9
9
 
@@ -16,7 +16,7 @@ from DefStream.Worker import request
16
16
 
17
17
 
18
18
  if __name__ == '__main__':
19
- LoggingSetup()()
19
+ TaskLoggingSetup()()
20
20
  broker = Connection(Configuration(
21
21
  host='127.0.0.1',
22
22
  port=5672,
@@ -28,9 +28,11 @@ from DefStream.Validator.Patterns.Object import (
28
28
  IsRequiredIn,
29
29
  )
30
30
  from DefStream.Worker import (
31
+ WorkerSetup,
31
32
  PoolSetup,
32
33
  Run,
33
34
  Specification,
35
+ Worker,
34
36
  )
35
37
 
36
38
  __all__ = (
@@ -42,7 +44,27 @@ __all__ = (
42
44
  logger = getLogger('Sample')
43
45
 
44
46
 
45
- class LoggingSetup(PoolSetup):
47
+ class SetupLogger(WorkerSetup):
48
+ """부모와 spawn 자식 프로세스의 샘플 로그 출력을 설정한다."""
49
+
50
+ def __call__(
51
+ self,
52
+ worker: Worker,
53
+ ) -> None:
54
+ handler = StreamHandler()
55
+ handler.setFormatter(Formatter('%(asctime)s %(processName)s %(name)s %(levelname)s %(message)s'))
56
+ sources = (
57
+ Filter('Sample'),
58
+ Filter('DefStream'),
59
+ )
60
+ handler.addFilter(lambda record: any(source.filter(record) for source in sources))
61
+ root = getLogger()
62
+ root.setLevel(DEBUG)
63
+ root.addHandler(handler)
64
+ return
65
+
66
+
67
+ class TaskLoggingSetup(PoolSetup):
46
68
  """부모와 spawn 자식 프로세스의 샘플 로그 출력을 설정한다."""
47
69
 
48
70
  def __call__(self) -> None:
@@ -7,7 +7,8 @@ from signal import (
7
7
  from Tasks import (
8
8
  Add,
9
9
  Divide,
10
- LoggingSetup,
10
+ TaskLoggingSetup,
11
+ SetupLogger,
11
12
  logger,
12
13
  )
13
14
 
@@ -76,7 +77,6 @@ class Cleanup(WorkerCleanup):
76
77
 
77
78
 
78
79
  if __name__ == '__main__':
79
- LoggingSetup()()
80
80
  worker = Worker(
81
81
  Connection(Configuration(
82
82
  host='127.0.0.1',
@@ -86,9 +86,12 @@ if __name__ == '__main__':
86
86
  )),
87
87
  ProcessPool(
88
88
  10,
89
- setups=LoggingSetup(),
89
+ setups=TaskLoggingSetup(),
90
+ ),
91
+ setups=(
92
+ SetupLogger(),
93
+ Prepare(),
90
94
  ),
91
- setups=Prepare(),
92
95
  cleanups=Cleanup(),
93
96
  inspector=Inspector(
94
97
  host='127.0.0.1',
@@ -39,19 +39,6 @@ from DefStream.Worker import (
39
39
  logger = getLogger('Sample')
40
40
 
41
41
 
42
- def configure_logging() -> None:
43
- handler = StreamHandler()
44
- handler.setFormatter(Formatter('%(asctime)s %(name)s %(levelname)s %(message)s'))
45
- sources = (
46
- Filter('Sample'),
47
- Filter('DefStream'),
48
- )
49
- handler.addFilter(lambda record: any(source.filter(record) for source in sources))
50
- root = getLogger()
51
- root.setLevel(DEBUG)
52
- root.addHandler(handler)
53
- return
54
-
55
42
 
56
43
  class Add(
57
44
  Run,
@@ -96,6 +83,24 @@ class Divide(
96
83
  return result
97
84
 
98
85
 
86
+ class SetupLogger(WorkerSetup):
87
+ def __call__(
88
+ self,
89
+ worker: Worker,
90
+ ) -> None:
91
+ handler = StreamHandler()
92
+ handler.setFormatter(Formatter('%(asctime)s %(name)s %(levelname)s %(message)s'))
93
+ sources = (
94
+ Filter('Sample'),
95
+ Filter('DefStream'),
96
+ )
97
+ handler.addFilter(lambda record: any(source.filter(record) for source in sources))
98
+ root = getLogger()
99
+ root.setLevel(DEBUG)
100
+ root.addHandler(handler)
101
+ return
102
+
103
+
99
104
  class Prepare(WorkerSetup):
100
105
  def __call__(
101
106
  self,
@@ -139,11 +144,13 @@ class Cleanup(WorkerCleanup):
139
144
 
140
145
 
141
146
  def main() -> None:
142
- configure_logging()
143
147
  worker = Worker(
144
148
  Connection(Configuration()),
145
149
  ThreadPool(10),
146
- setups=Prepare(),
150
+ setups=(
151
+ SetupLogger(),
152
+ Prepare(),
153
+ ),
147
154
  cleanups=Cleanup(),
148
155
  inspector=Inspector(
149
156
  host='127.0.0.1',
@@ -35,19 +35,6 @@ from DefStream.Worker import (
35
35
  logger = getLogger('Sample')
36
36
 
37
37
 
38
- def configure_logging() -> None:
39
- handler = StreamHandler()
40
- handler.setFormatter(Formatter('%(asctime)s %(name)s %(levelname)s %(message)s'))
41
- sources = (
42
- Filter('Sample'),
43
- Filter('DefStream'),
44
- )
45
- handler.addFilter(lambda record: any(source.filter(record) for source in sources))
46
- root = getLogger()
47
- root.setLevel(DEBUG)
48
- root.addHandler(handler)
49
- return
50
-
51
38
 
52
39
  class Queue(Sendable):
53
40
  def __init__(self):
@@ -219,6 +206,24 @@ class Add(
219
206
  )
220
207
 
221
208
 
209
+ class SetupLogger(WorkerSetup):
210
+ def __call__(
211
+ self,
212
+ worker: Worker,
213
+ ) -> None:
214
+ handler = StreamHandler()
215
+ handler.setFormatter(Formatter('%(asctime)s %(name)s %(levelname)s %(message)s'))
216
+ sources = (
217
+ Filter('Sample'),
218
+ Filter('DefStream'),
219
+ )
220
+ handler.addFilter(lambda record: any(source.filter(record) for source in sources))
221
+ root = getLogger()
222
+ root.setLevel(DEBUG)
223
+ root.addHandler(handler)
224
+ return
225
+
226
+
222
227
  class Prepare(WorkerSetup):
223
228
  def __call__(
224
229
  self,
@@ -252,12 +257,16 @@ class Cleanup(WorkerCleanup):
252
257
 
253
258
 
254
259
  if __name__ == '__main__':
255
- configure_logging()
256
260
  worker = Worker(
257
261
  Broker(),
258
262
  ThreadPool(1),
259
- setups=Prepare(),
260
- cleanups=Cleanup(),
263
+ setups=(
264
+ SetupLogger(),
265
+ Prepare(),
266
+ ),
267
+ cleanups=(
268
+ Cleanup(),
269
+ ),
261
270
  )
262
271
  worker.add(Add)
263
272
  worker.run()
@@ -323,13 +323,13 @@ class Console(Extension):
323
323
  )
324
324
  task = next(
325
325
  (
326
- task for task in worker.tasks if task.__name__ == payload['task']
326
+ task for task in worker.tasks if task.name == payload['task']
327
327
  ),
328
328
  None,
329
329
  )
330
330
  if task is None:
331
331
  raise ValueError('Unknown registered task')
332
- if sum(task.__name__ == payload['task'] for task in worker.tasks) != 1:
332
+ if sum(task.name == payload['task'] for task in worker.tasks) != 1:
333
333
  raise ValueError('Ambiguous registered task name')
334
334
  except (
335
335
  ValueError,
@@ -337,8 +337,9 @@ class Console(Extension):
337
337
  UnicodeDecodeError,
338
338
  ) as error:
339
339
  self.logger.warning(
340
- 'Inspector Console rejected invalid input: error=%s',
340
+ 'Inspector Console rejected invalid input: error=%s message=%s',
341
341
  type(error).__name__,
342
+ error,
342
343
  )
343
344
  return json_response(
344
345
  '400 Bad Request',
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Mapping
2
- from os import getpid
2
+ from os import environ, getpid
3
+ import sys
3
4
  from typing import (
4
5
  Any,
5
6
  TYPE_CHECKING,
@@ -19,9 +20,42 @@ LOGO = r"""
19
20
  ███ ███ ▄█▀█▄ ▀██▀ ▀████▄ ▀██▀▀ ████▄ ▄█▀█▄ ▀▀█▄ ███▄███▄
20
21
  ███ ███ ██▄█▀ ██ ▀████ ██ ██ ▀▀ ██▄█▀ ▄█▀██ ██ ██ ██
21
22
  ██████▀ ▀█▄▄▄ ██ ███████▀ ██ ██ ▀█▄▄▄ ▀█▄██ ██ ██ ██
23
+
24
+ Just def. Let workers do the work. You don't need to await this.
22
25
  """
23
26
 
24
27
 
28
+ def supports_color() -> bool:
29
+ return (
30
+ getattr(sys.stdout, 'isatty', lambda: False)()
31
+ and not environ.get('NO_COLOR')
32
+ )
33
+
34
+
35
+ def colorize_logo(logo: str) -> str:
36
+ colors = (
37
+ '\033[38;5;39m',
38
+ '\033[38;5;45m',
39
+ '\033[38;5;51m',
40
+ '\033[38;5;45m',
41
+ '\033[38;5;39m',
42
+ )
43
+ reset = '\033[0m'
44
+ colorized_lines = []
45
+ art_line = 0
46
+ for line in logo.splitlines(keepends=True):
47
+ if line.strip() and art_line < len(colors):
48
+ newline = '\n' if line.endswith('\n') else ''
49
+ colorized_lines.append(
50
+ f'{colors[art_line]}{line[:-1] if newline else line}'
51
+ f'{reset}{newline}',
52
+ )
53
+ art_line += 1
54
+ else:
55
+ colorized_lines.append(line)
56
+ return ''.join(colorized_lines)
57
+
58
+
25
59
  def subscription_value(
26
60
  parameters: Any,
27
61
  name: str,
@@ -90,10 +124,15 @@ def startup(worker: 'Worker') -> None:
90
124
  str,
91
125
  ) and vhost:
92
126
  broker_name += '/' + vhost.lstrip('/')
127
+ logo = LOGO.rstrip()
128
+ starting_worker = 'Starting Worker'
129
+ if supports_color():
130
+ logo = colorize_logo(logo)
131
+ starting_worker = f'\033[1;38;5;51m{starting_worker}\033[0m'
93
132
  lines = [
94
- LOGO.rstrip(),
133
+ logo,
95
134
  '',
96
- 'Starting Worker',
135
+ starting_worker,
97
136
  f' PID: {getpid()}',
98
137
  f' Broker: {broker_name}',
99
138
  ]
@@ -57,6 +57,19 @@ class Task(
57
57
  return a / b
58
58
 
59
59
 
60
+ class NamedTask(
61
+ Run,
62
+ run_name='ETL.OBS.Split',
63
+ parameters={'queue': 'named-task'},
64
+ ):
65
+ def run(
66
+ self,
67
+ a,
68
+ b,
69
+ ):
70
+ return a / b
71
+
72
+
60
73
  class Producer:
61
74
  def __init__(self):
62
75
  self.connected = 0
@@ -168,7 +181,7 @@ class ConsoleExtensionTests(TestCase):
168
181
  mode='send',
169
182
  ):
170
183
  return {
171
- 'task': 'Task',
184
+ 'task': Task.name,
172
185
  'mode': mode,
173
186
  'body': {
174
187
  'a': 6,
@@ -459,6 +472,47 @@ class ConsoleExtensionTests(TestCase):
459
472
  self.worker.broker.connect.assert_not_called()
460
473
  self.worker.broker.close.assert_not_called()
461
474
 
475
+ def test_custom_task_name_is_resolved_and_duplicate_names_are_rejected(self):
476
+ self.worker.add(NamedTask)
477
+ payload = self.payload()
478
+ payload['task'] = NamedTask.name
479
+ status, body, headers = self.fetch(
480
+ 'POST',
481
+ payload,
482
+ )
483
+ self.assertEqual(status, 200)
484
+ self.assertEqual(loads(body)['result'], 'message-id')
485
+ self.assertEqual(self.producers[-1].sent[0][1], NamedTask.parameters)
486
+
487
+ self.worker.tasks.append(NamedTask)
488
+ with self.assertLogs(
489
+ 'DefStream.Inspector.Extensions.Console',
490
+ level='WARNING',
491
+ ) as logs:
492
+ status, body, headers = self.fetch(
493
+ 'POST',
494
+ payload,
495
+ )
496
+ self.assertEqual(status, 400)
497
+ self.assertEqual(loads(body)['error'], 'Ambiguous registered task name')
498
+ self.assertIn('message=Ambiguous registered task name', logs.output[0])
499
+
500
+ def test_invalid_task_warning_includes_the_specific_error(self):
501
+ payload = self.payload()
502
+ payload['task'] = 'not-registered'
503
+ with self.assertLogs(
504
+ 'DefStream.Inspector.Extensions.Console',
505
+ level='WARNING',
506
+ ) as logs:
507
+ status, body, headers = self.fetch(
508
+ 'POST',
509
+ payload,
510
+ )
511
+ self.assertEqual(status, 400)
512
+ self.assertIn('error=ValueError', logs.output[0])
513
+ self.assertIn('message=Unknown registered task', logs.output[0])
514
+ self.assertEqual(self.producers, [])
515
+
462
516
  def test_unsupported_rpc_and_worker_not_running(self):
463
517
  self.extension.broker = Producer
464
518
  self.assertFalse(loads(self.fetch()[1])['rpc'])
@@ -768,7 +822,7 @@ class ConsoleExtensionTests(TestCase):
768
822
  rpc,
769
823
  )
770
824
  payload = self.payload()
771
- payload['task'] = 'Live'
825
+ payload['task'] = Live.name
772
826
  self.assertEqual(
773
827
  self.fetch(
774
828
  'POST',
@@ -274,7 +274,7 @@ class LoggingTests(TestCase):
274
274
  try:
275
275
  spec.loader.exec_module(sample)
276
276
  self.assertEqual(root.handlers, handlers)
277
- sample.configure_logging()
277
+ sample.SetupLogger()(Mock())
278
278
  handler = root.handlers[-1]
279
279
  for name, expected in (
280
280
  (
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes