DefEssential 0.1.6__tar.gz → 0.1.8__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 (195) hide show
  1. defessential-0.1.8/DefEssential.egg-info/PKG-INFO +82 -0
  2. {defessential-0.1.6 → defessential-0.1.8}/DefEssential.egg-info/SOURCES.txt +12 -0
  3. defessential-0.1.8/PKG-INFO +82 -0
  4. defessential-0.1.8/README.en.md +133 -0
  5. defessential-0.1.8/README.md +133 -0
  6. defessential-0.1.8/README.setup.md +48 -0
  7. defessential-0.1.8/issues/feature/3/IMPLEMENTS.md +118 -0
  8. defessential-0.1.8/issues/feature/3/ISSUE.md +9 -0
  9. defessential-0.1.8/issues/feature/3/README.md +48 -0
  10. defessential-0.1.8/issues/feature/3/REQUIREMENTS.md +326 -0
  11. defessential-0.1.8/issues/task/14/ISSUE.md +30 -0
  12. defessential-0.1.8/issues/task/14/PLAN.md +35 -0
  13. defessential-0.1.8/issues/task/14/README.md +7 -0
  14. defessential-0.1.8/issues/task/14/REQUIREMENTS.md +84 -0
  15. defessential-0.1.8/issues/task/14/TASK.md +22 -0
  16. {defessential-0.1.6 → defessential-0.1.8}/samples/Sample.py +72 -24
  17. {defessential-0.1.6 → defessential-0.1.8}/samples/runners/Requests.py +10 -21
  18. {defessential-0.1.6 → defessential-0.1.8}/samples/runners/Streaming.py +11 -22
  19. defessential-0.1.8/samples/runners/__init__.py +1 -0
  20. {defessential-0.1.6 → defessential-0.1.8}/samples/scripts/DescriptionToFile.py +1 -1
  21. {defessential-0.1.6 → defessential-0.1.8}/setup.py +1 -1
  22. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Gunicorn/Server.py +21 -3
  23. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Gunicorn/Transport.py +4 -4
  24. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Python/Server.py +47 -19
  25. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Uvicorn/HTTP.py +2 -2
  26. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Uvicorn/Portal.py +1 -1
  27. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Uvicorn/Server.py +40 -5
  28. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Uvicorn/WebSocket.py +6 -4
  29. defessential-0.1.8/src/Adapter/Implements/__init__.py +3 -0
  30. defessential-0.1.8/src/Adapter/RequestHandler.py +109 -0
  31. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Server.py +4 -4
  32. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/WSGI.py +4 -4
  33. {defessential-0.1.6 → defessential-0.1.8}/src/Application.py +97 -19
  34. {defessential-0.1.6 → defessential-0.1.8}/src/CrossOriginResourceSharing.py +1 -1
  35. {defessential-0.1.6 → defessential-0.1.8}/src/Extension.py +17 -10
  36. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/File.py +8 -2
  37. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/FileSystem.py +15 -4
  38. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Metrics.py +27 -16
  39. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Ping.py +5 -1
  40. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Specifications.py +16 -2
  41. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Status.py +8 -2
  42. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Swagger.py +8 -2
  43. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Utils/Routes.py +1 -1
  44. {defessential-0.1.6 → defessential-0.1.8}/src/IO/RequestReader.py +2 -2
  45. {defessential-0.1.6 → defessential-0.1.8}/src/IO/ResponseWriter.py +5 -4
  46. {defessential-0.1.6 → defessential-0.1.8}/src/IO/WebSocket/WebSocket.py +16 -10
  47. {defessential-0.1.6 → defessential-0.1.8}/src/IO/WebSocket/WebSocketError.py +1 -1
  48. {defessential-0.1.6 → defessential-0.1.8}/src/IO/WebSocket/WebSocketFrame.py +1 -1
  49. {defessential-0.1.6 → defessential-0.1.8}/src/IO/WebSocket/WebSocketFrameReader.py +2 -2
  50. {defessential-0.1.6 → defessential-0.1.8}/src/IO/WebSocket/WebSocketHandshake.py +3 -3
  51. {defessential-0.1.6 → defessential-0.1.8}/src/RequestRunner.py +4 -4
  52. {defessential-0.1.6 → defessential-0.1.8}/src/Response.py +4 -2
  53. {defessential-0.1.6 → defessential-0.1.8}/src/Runners/RunExtension.py +20 -5
  54. {defessential-0.1.6 → defessential-0.1.8}/src/Runners/RunFactory.py +1 -1
  55. defessential-0.1.8/src/Runners/RunRequest.py +198 -0
  56. {defessential-0.1.6 → defessential-0.1.8}/src/Startup.py +2 -2
  57. {defessential-0.1.6 → defessential-0.1.8}/src/Validators.py +1 -1
  58. {defessential-0.1.6 → defessential-0.1.8}/tests/test_adapters.py +39 -6
  59. {defessential-0.1.6 → defessential-0.1.8}/tests/test_application_hooks.py +1 -1
  60. {defessential-0.1.6 → defessential-0.1.8}/tests/test_builtin_factories.py +14 -0
  61. {defessential-0.1.6 → defessential-0.1.8}/tests/test_exports.py +1 -1
  62. {defessential-0.1.6 → defessential-0.1.8}/tests/test_extension_hooks.py +1 -1
  63. defessential-0.1.8/tests/test_logging.py +835 -0
  64. {defessential-0.1.6 → defessential-0.1.8}/tests/test_request_runner_hooks.py +30 -1
  65. {defessential-0.1.6 → defessential-0.1.8}/tests/test_samples.py +36 -19
  66. {defessential-0.1.6 → defessential-0.1.8}/tests/test_server.py +1 -1
  67. {defessential-0.1.6 → defessential-0.1.8}/tests/test_transport_conformance.py +3 -3
  68. {defessential-0.1.6 → defessential-0.1.8}/tests/test_websocket.py +3 -3
  69. {defessential-0.1.6 → defessential-0.1.8}/tests/test_writers.py +7 -3
  70. defessential-0.1.6/DefEssential.egg-info/PKG-INFO +0 -41
  71. defessential-0.1.6/PKG-INFO +0 -41
  72. defessential-0.1.6/README.md +0 -7
  73. defessential-0.1.6/samples/runners/__init__.py +0 -1
  74. defessential-0.1.6/src/Adapter/Implements/__init__.py +0 -3
  75. defessential-0.1.6/src/Adapter/RequestHandler.py +0 -70
  76. defessential-0.1.6/src/Runners/RunRequest.py +0 -121
  77. {defessential-0.1.6 → defessential-0.1.8}/.github/ISSUE_TEMPLATE/01.feature.yml +0 -0
  78. {defessential-0.1.6 → defessential-0.1.8}/.github/ISSUE_TEMPLATE/02.bug.yml +0 -0
  79. {defessential-0.1.6 → defessential-0.1.8}/.github/ISSUE_TEMPLATE/03.fix.yml +0 -0
  80. {defessential-0.1.6 → defessential-0.1.8}/.github/ISSUE_TEMPLATE/04.task.yml +0 -0
  81. {defessential-0.1.6 → defessential-0.1.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  82. {defessential-0.1.6 → defessential-0.1.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  83. {defessential-0.1.6 → defessential-0.1.8}/.github/workflows/ci.yml +0 -0
  84. {defessential-0.1.6 → defessential-0.1.8}/.gitignore +0 -0
  85. {defessential-0.1.6 → defessential-0.1.8}/DefEssential.egg-info/dependency_links.txt +0 -0
  86. {defessential-0.1.6 → defessential-0.1.8}/DefEssential.egg-info/requires.txt +0 -0
  87. {defessential-0.1.6 → defessential-0.1.8}/DefEssential.egg-info/top_level.txt +0 -0
  88. {defessential-0.1.6 → defessential-0.1.8}/LICENSE +0 -0
  89. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/2/ISSUE.md +0 -0
  90. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/2/README.md +0 -0
  91. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/2/REQUIREMENTS.md +0 -0
  92. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/4/IMPLEMENTS.md +0 -0
  93. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/4/ISSUE.md +0 -0
  94. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/4/PLAN.md +0 -0
  95. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/4/README.md +0 -0
  96. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/4/REQUIREMENTS.md +0 -0
  97. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/5/IMPLEMENTS.md +0 -0
  98. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/5/ISSUE.md +0 -0
  99. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/5/PLAN.md +0 -0
  100. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/5/README.md +0 -0
  101. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/5/REQUIREMENTS.md +0 -0
  102. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/6/IMPLEMENTS.md +0 -0
  103. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/6/ISSUE.md +0 -0
  104. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/6/PLAN.md +0 -0
  105. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/6/README.md +0 -0
  106. {defessential-0.1.6 → defessential-0.1.8}/issues/feature/6/REQUIREMENTS.md +0 -0
  107. {defessential-0.1.6 → defessential-0.1.8}/issues/task/8/IMPLEMENTS.md +0 -0
  108. {defessential-0.1.6 → defessential-0.1.8}/issues/task/8/ISSUE.md +0 -0
  109. {defessential-0.1.6 → defessential-0.1.8}/issues/task/8/PLAN.md +0 -0
  110. {defessential-0.1.6 → defessential-0.1.8}/issues/task/8/README.md +0 -0
  111. {defessential-0.1.6 → defessential-0.1.8}/issues/task/8/REQUIREMENTS.md +0 -0
  112. {defessential-0.1.6 → defessential-0.1.8}/samples/README.md +0 -0
  113. {defessential-0.1.6 → defessential-0.1.8}/samples/__init__.py +0 -0
  114. {defessential-0.1.6 → defessential-0.1.8}/samples/res/css/sample.css +0 -0
  115. {defessential-0.1.6 → defessential-0.1.8}/samples/res/favicon.ico +0 -0
  116. {defessential-0.1.6 → defessential-0.1.8}/samples/res/html/welcome.html +0 -0
  117. {defessential-0.1.6 → defessential-0.1.8}/samples/res/images/sample.jpg +0 -0
  118. {defessential-0.1.6 → defessential-0.1.8}/samples/res/images/sample.png +0 -0
  119. {defessential-0.1.6 → defessential-0.1.8}/samples/runners/Authentication.py +0 -0
  120. {defessential-0.1.6 → defessential-0.1.8}/samples/runners/ContentValidation.py +0 -0
  121. {defessential-0.1.6 → defessential-0.1.8}/samples/scripts/__init__.py +0 -0
  122. {defessential-0.1.6 → defessential-0.1.8}/setup.cfg +0 -0
  123. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Gunicorn/__init__.py +0 -0
  124. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Python/__init__.py +0 -0
  125. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/Implements/Uvicorn/__init__.py +0 -0
  126. {defessential-0.1.6 → defessential-0.1.8}/src/Adapter/__init__.py +0 -0
  127. {defessential-0.1.6 → defessential-0.1.8}/src/Body.py +0 -0
  128. {defessential-0.1.6 → defessential-0.1.8}/src/Content.py +0 -0
  129. {defessential-0.1.6 → defessential-0.1.8}/src/Credentials.py +0 -0
  130. {defessential-0.1.6 → defessential-0.1.8}/src/Descriptor/DataObject.py +0 -0
  131. {defessential-0.1.6 → defessential-0.1.8}/src/Descriptor/Types.py +0 -0
  132. {defessential-0.1.6 → defessential-0.1.8}/src/Descriptor/Value.py +0 -0
  133. {defessential-0.1.6 → defessential-0.1.8}/src/Descriptor/__init__.py +0 -0
  134. {defessential-0.1.6 → defessential-0.1.8}/src/Error.py +0 -0
  135. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Status.html +0 -0
  136. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Swagger.html +0 -0
  137. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/Utils/__init__.py +0 -0
  138. {defessential-0.1.6 → defessential-0.1.8}/src/Extensions/__init__.py +0 -0
  139. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Readers/ChunkedStream.py +0 -0
  140. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Readers/ContentReader.py +0 -0
  141. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Readers/ContentReaders.py +0 -0
  142. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Readers/__init__.py +0 -0
  143. {defessential-0.1.6 → defessential-0.1.8}/src/IO/WebSocket/__init__.py +0 -0
  144. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Writers/ChunkedStreamWriter.py +0 -0
  145. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Writers/ServerSentEvents.py +0 -0
  146. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Writers/ServerSentEventsWriter.py +0 -0
  147. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Writers/StreamWriter.py +0 -0
  148. {defessential-0.1.6 → defessential-0.1.8}/src/IO/Writers/__init__.py +0 -0
  149. {defessential-0.1.6 → defessential-0.1.8}/src/IO/__init__.py +0 -0
  150. {defessential-0.1.6 → defessential-0.1.8}/src/Request.py +0 -0
  151. {defessential-0.1.6 → defessential-0.1.8}/src/ResponseSpecification.py +0 -0
  152. {defessential-0.1.6 → defessential-0.1.8}/src/Responses/ResponseFile.py +0 -0
  153. {defessential-0.1.6 → defessential-0.1.8}/src/Responses/__init__.py +0 -0
  154. {defessential-0.1.6 → defessential-0.1.8}/src/Route.py +0 -0
  155. {defessential-0.1.6 → defessential-0.1.8}/src/Router.py +0 -0
  156. {defessential-0.1.6 → defessential-0.1.8}/src/Runners/RunOptions.py +0 -0
  157. {defessential-0.1.6 → defessential-0.1.8}/src/Runners/__init__.py +0 -0
  158. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Configuration.py +0 -0
  159. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Connection.py +0 -0
  160. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/File.py +0 -0
  161. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Implements/FileSystem/Configuration.py +0 -0
  162. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Implements/FileSystem/Connection.py +0 -0
  163. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Implements/FileSystem/File.py +0 -0
  164. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Implements/FileSystem/__init__.py +0 -0
  165. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/Implements/__init__.py +0 -0
  166. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/FileSystemObject/__init__.py +0 -0
  167. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/Loader.py +0 -0
  168. {defessential-0.1.6 → defessential-0.1.8}/src/Utils/__init__.py +0 -0
  169. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Pattern.py +0 -0
  170. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Array.py +0 -0
  171. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Common.py +0 -0
  172. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Compare.py +0 -0
  173. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Condition.py +0 -0
  174. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/DataObject.py +0 -0
  175. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/DateTime.py +0 -0
  176. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Number.py +0 -0
  177. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Object.py +0 -0
  178. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Size.py +0 -0
  179. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/String.py +0 -0
  180. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/Type.py +0 -0
  181. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Patterns/__init__.py +0 -0
  182. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Validate.py +0 -0
  183. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/Validator.py +0 -0
  184. {defessential-0.1.6 → defessential-0.1.8}/src/Validator/__init__.py +0 -0
  185. {defessential-0.1.6 → defessential-0.1.8}/src/__init__.py +0 -0
  186. {defessential-0.1.6 → defessential-0.1.8}/tests/test_content.py +0 -0
  187. {defessential-0.1.6 → defessential-0.1.8}/tests/test_core.py +0 -0
  188. {defessential-0.1.6 → defessential-0.1.8}/tests/test_extensions.py +0 -0
  189. {defessential-0.1.6 → defessential-0.1.8}/tests/test_features.py +0 -0
  190. {defessential-0.1.6 → defessential-0.1.8}/tests/test_files.py +0 -0
  191. {defessential-0.1.6 → defessential-0.1.8}/tests/test_independence.py +0 -0
  192. {defessential-0.1.6 → defessential-0.1.8}/tests/test_loading.py +0 -0
  193. {defessential-0.1.6 → defessential-0.1.8}/tests/test_sse.py +0 -0
  194. {defessential-0.1.6 → defessential-0.1.8}/tests/test_startup.py +0 -0
  195. {defessential-0.1.6 → defessential-0.1.8}/tests/test_uvicorn_transport.py +0 -0
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: DefEssential
3
+ Version: 0.1.8
4
+ Summary: The Modern Synchronous Python Web Application Framework
5
+ Home-page: https://github.com/yong5eon/DefEssential
6
+ Author: Heo Yongseon
7
+ Project-URL: Source, https://github.com/yong5eon/DefEssential
8
+ Project-URL: Issues, https://github.com/yong5eon/DefEssential/issues
9
+ Project-URL: Releases, https://github.com/yong5eon/DefEssential/releases
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: gunicorn
20
+ Requires-Dist: gunicorn>=23; extra == "gunicorn"
21
+ Provides-Extra: uvicorn
22
+ Requires-Dist: uvicorn>=0.30; extra == "uvicorn"
23
+ Requires-Dist: a2wsgi>=1.10; extra == "uvicorn"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+ Dynamic: project-url
31
+ Dynamic: provides-extra
32
+ Dynamic: requires-python
33
+ Dynamic: summary
34
+
35
+ # DefEssential
36
+
37
+ DefEssential is a synchronous Python web application framework for handling HTTP requests with ordinary Python methods. It provides route declarations, request validation, response helpers, streaming APIs, lifecycle hooks, and optional extensions without requiring `async` / `await`.
38
+
39
+ ## Requirements
40
+
41
+ - Python 3.9 or later
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install DefEssential
47
+ ```
48
+
49
+ The package includes a built-in Python HTTP server. Optional extras provide additional server adapters:
50
+
51
+ ```bash
52
+ pip install "DefEssential[Gunicorn]"
53
+ pip install "DefEssential[Uvicorn]"
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ ```python
59
+ from DefEssential import Application, Request, RequestRunner
60
+ from DefEssential.Adapter.Implements.Python import Server
61
+ from DefEssential.Responses import ResponseText
62
+
63
+
64
+ class Hello(RequestRunner, method='GET', url='/hello'):
65
+ def run(self, request: Request):
66
+ return ResponseText('Hello, world!')
67
+
68
+
69
+ application = Application()
70
+ application.add(Hello)
71
+ Server('127.0.0.1', 8000, application).run()
72
+ ```
73
+
74
+ The handler declares an HTTP method and path on its `RequestRunner` class. The application registers the handler, and the server runs the application. A `GET /hello` request returns a plain-text response.
75
+
76
+ ## Request handling
77
+
78
+ Define handlers as `RequestRunner` subclasses and implement `run()` with synchronous Python code. Configure path, query, header, and body validation in the class declaration. The framework also provides JSON, text, and file responses, direct streaming, Server-Sent Events, WebSocket support, and extensions for status checks, metrics, OpenAPI specifications, Swagger UI, and file serving.
79
+
80
+ ## License
81
+
82
+ See the `LICENSE` file included with the source distribution for license terms.
@@ -1,6 +1,8 @@
1
1
  .gitignore
2
2
  LICENSE
3
+ README.en.md
3
4
  README.md
5
+ README.setup.md
4
6
  setup.py
5
7
  .github/PULL_REQUEST_TEMPLATE.md
6
8
  .github/ISSUE_TEMPLATE/01.feature.yml
@@ -17,6 +19,10 @@ DefEssential.egg-info/top_level.txt
17
19
  issues/feature/2/ISSUE.md
18
20
  issues/feature/2/README.md
19
21
  issues/feature/2/REQUIREMENTS.md
22
+ issues/feature/3/IMPLEMENTS.md
23
+ issues/feature/3/ISSUE.md
24
+ issues/feature/3/README.md
25
+ issues/feature/3/REQUIREMENTS.md
20
26
  issues/feature/4/IMPLEMENTS.md
21
27
  issues/feature/4/ISSUE.md
22
28
  issues/feature/4/PLAN.md
@@ -32,6 +38,11 @@ issues/feature/6/ISSUE.md
32
38
  issues/feature/6/PLAN.md
33
39
  issues/feature/6/README.md
34
40
  issues/feature/6/REQUIREMENTS.md
41
+ issues/task/14/ISSUE.md
42
+ issues/task/14/PLAN.md
43
+ issues/task/14/README.md
44
+ issues/task/14/REQUIREMENTS.md
45
+ issues/task/14/TASK.md
35
46
  issues/task/8/IMPLEMENTS.md
36
47
  issues/task/8/ISSUE.md
37
48
  issues/task/8/PLAN.md
@@ -163,6 +174,7 @@ tests/test_features.py
163
174
  tests/test_files.py
164
175
  tests/test_independence.py
165
176
  tests/test_loading.py
177
+ tests/test_logging.py
166
178
  tests/test_request_runner_hooks.py
167
179
  tests/test_samples.py
168
180
  tests/test_server.py
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: DefEssential
3
+ Version: 0.1.8
4
+ Summary: The Modern Synchronous Python Web Application Framework
5
+ Home-page: https://github.com/yong5eon/DefEssential
6
+ Author: Heo Yongseon
7
+ Project-URL: Source, https://github.com/yong5eon/DefEssential
8
+ Project-URL: Issues, https://github.com/yong5eon/DefEssential/issues
9
+ Project-URL: Releases, https://github.com/yong5eon/DefEssential/releases
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: gunicorn
20
+ Requires-Dist: gunicorn>=23; extra == "gunicorn"
21
+ Provides-Extra: uvicorn
22
+ Requires-Dist: uvicorn>=0.30; extra == "uvicorn"
23
+ Requires-Dist: a2wsgi>=1.10; extra == "uvicorn"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+ Dynamic: project-url
31
+ Dynamic: provides-extra
32
+ Dynamic: requires-python
33
+ Dynamic: summary
34
+
35
+ # DefEssential
36
+
37
+ DefEssential is a synchronous Python web application framework for handling HTTP requests with ordinary Python methods. It provides route declarations, request validation, response helpers, streaming APIs, lifecycle hooks, and optional extensions without requiring `async` / `await`.
38
+
39
+ ## Requirements
40
+
41
+ - Python 3.9 or later
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install DefEssential
47
+ ```
48
+
49
+ The package includes a built-in Python HTTP server. Optional extras provide additional server adapters:
50
+
51
+ ```bash
52
+ pip install "DefEssential[Gunicorn]"
53
+ pip install "DefEssential[Uvicorn]"
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ ```python
59
+ from DefEssential import Application, Request, RequestRunner
60
+ from DefEssential.Adapter.Implements.Python import Server
61
+ from DefEssential.Responses import ResponseText
62
+
63
+
64
+ class Hello(RequestRunner, method='GET', url='/hello'):
65
+ def run(self, request: Request):
66
+ return ResponseText('Hello, world!')
67
+
68
+
69
+ application = Application()
70
+ application.add(Hello)
71
+ Server('127.0.0.1', 8000, application).run()
72
+ ```
73
+
74
+ The handler declares an HTTP method and path on its `RequestRunner` class. The application registers the handler, and the server runs the application. A `GET /hello` request returns a plain-text response.
75
+
76
+ ## Request handling
77
+
78
+ Define handlers as `RequestRunner` subclasses and implement `run()` with synchronous Python code. Configure path, query, header, and body validation in the class declaration. The framework also provides JSON, text, and file responses, direct streaming, Server-Sent Events, WebSocket support, and extensions for status checks, metrics, OpenAPI specifications, Swagger UI, and file serving.
79
+
80
+ ## License
81
+
82
+ See the `LICENSE` file included with the source distribution for license terms.
@@ -0,0 +1,133 @@
1
+ # DefEssential
2
+
3
+ The modern synchronous Python web application framework
4
+
5
+ **Just def.**<br>
6
+ **Let web apps do the work.**<br>
7
+ **You don't need to await this.**
8
+
9
+ [한국어](./README.md)
10
+
11
+ DefEssential is a synchronous web framework for handling HTTP requests with ordinary Python methods.
12
+ Configure routes, request validation, responses, and extensions without `async` / `await`.
13
+
14
+ - **Synchronous request handling**: Declare HTTP methods, paths, and handlers with `RequestRunner` classes.
15
+ - **Request validation**: Apply validators and converters to path, query, header, and body values.
16
+ - **Responses and streaming**: Support JSON, text, and file responses, streaming, Server-Sent Events, and WebSocket.
17
+ - **Extensions**: Combine health status, Ping, Metrics, OpenAPI specifications, Swagger UI, and file-serving features.
18
+ - **Server adapters**: Use the built-in Python server or optionally install the Gunicorn and Uvicorn adapters.
19
+ - **Lifecycle hooks**: Register hooks for application, extension, and request execution lifecycles.
20
+
21
+ ## Requirements and installation
22
+
23
+ - Python 3.9 or later
24
+
25
+ ```bash
26
+ pip install DefEssential
27
+ ```
28
+
29
+ The built-in Python server requires no additional server dependency. Install an optional extra to use another adapter.
30
+
31
+ ```bash
32
+ pip install "DefEssential[Gunicorn]"
33
+ pip install "DefEssential[Uvicorn]"
34
+ ```
35
+
36
+ ## Quick start
37
+
38
+ ```python
39
+ from DefEssential import Application, Request, RequestRunner
40
+ from DefEssential.Adapter.Implements.Python import Server
41
+ from DefEssential.Responses import ResponseText
42
+
43
+
44
+ class Hello(RequestRunner, method='GET', url='/hello'):
45
+ def run(self, request: Request):
46
+ return ResponseText('Hello, world!')
47
+
48
+
49
+ application = Application()
50
+ application.add(Hello)
51
+
52
+ Server('127.0.0.1', 8000, application).run()
53
+ ```
54
+
55
+ After starting the server, a `GET /hello` request returns `Hello, world!`. Each request is handled by a new handler instance.
56
+
57
+ ## Request handlers and validation
58
+
59
+ Declare `method` and `url` on a `RequestRunner` subclass and implement synchronous request handling in `run()`. Declare URL path parameters with the `:name` syntax and configure validation rules with `parameters`, `qs`, `headers`, and `body`.
60
+
61
+ ```python
62
+ from DefEssential import (
63
+ Parameters,
64
+ Request,
65
+ RequestRunner,
66
+ )
67
+ from DefEssential.Responses import ResponseJSON
68
+ from DefEssential.Validator.Patterns import (
69
+ IsGreaterThan,
70
+ ToInteger,
71
+ )
72
+
73
+
74
+ class ReadValue(
75
+ RequestRunner,
76
+ method='GET',
77
+ url='/values/:value',
78
+ parameters=Parameters({
79
+ 'value': ToInteger(IsGreaterThan(0)),
80
+ }),
81
+ ):
82
+ def run(self, request: Request):
83
+ return ResponseJSON({'value': request.parameters['value']})
84
+ ```
85
+
86
+ Validators and converters run before input is passed to the handler. Read request bodies as JSON, text, binary data, or streams through `Body` and content readers.
87
+
88
+ ## Responses, streaming, and protocols
89
+
90
+ - Create JSON, text, and file responses with `ResponseJSON`, `ResponseText`, and `ResponseFile`.
91
+ - Stream response bodies through `request.writer` and stream writers. In this mode, the handler writes the response directly and returns `None`.
92
+ - Send event streams with the Server-Sent Events writer.
93
+ - Use the `WebSocket` API to exchange text and binary messages after upgrading an HTTP connection.
94
+
95
+ Direct-output and streaming responses have different processing timing from ordinary returned responses. Configure response handling according to the relevant hook contracts and transport behavior.
96
+
97
+ ## Extensions and servers
98
+
99
+ Pass extensions to `Application(exts=...)`. Built-in extensions include:
100
+
101
+ - `Status`: provide a status page
102
+ - `Ping`: report the application's running state
103
+ - `Metrics`: expose request metrics as JSON or Prometheus
104
+ - `Specifications`: generate an OpenAPI 3.1 document from registered routes
105
+ - `Swagger`: provide Swagger UI
106
+ - `File`, `FileSystem`: serve fixed files or file-system content
107
+
108
+ The built-in server is `DefEssential.Adapter.Implements.Python.Server`. Install the `DefEssential[Gunicorn]` or `DefEssential[Uvicorn]` extra for the corresponding adapter. Supported environments and transport characteristics vary by adapter and worker type.
109
+
110
+ ## Lifecycle hooks and logging
111
+
112
+ `Application` accepts `on_startup`, `on_cleanup`, `on_request`, `on_request_response`, and `on_request_error` hooks. `Extension` supports startup, cleanup, request, response, and error hooks; `RequestRunner` supports request, response, and error hooks.
113
+
114
+ DefEssential uses standard `logging` loggers and does not configure application handlers or log levels. Configure the loggers and output policy required by your application.
115
+
116
+ ## Samples and tests
117
+
118
+ `samples/Sample.py` in the repository demonstrates requests, validation, authentication, files, streaming, WebSocket, and built-in extensions. Run the sample server from the project root:
119
+
120
+ ```bash
121
+ python samples/Sample.py
122
+ ```
123
+
124
+ Install the current checkout in editable mode from the project root, then run the tests:
125
+
126
+ ```bash
127
+ pip install -e .
128
+ python -m unittest discover -s tests
129
+ ```
130
+
131
+ ## License
132
+
133
+ See the `LICENSE` file in the repository for license terms.
@@ -0,0 +1,133 @@
1
+ # DefEssential
2
+
3
+ 동기식 Python 웹 애플리케이션 프레임워크
4
+
5
+ **Just def.**<br>
6
+ **Let web apps do the work.**<br>
7
+ **You don't need to await this.**
8
+
9
+ [English](./README.en.md)
10
+
11
+ DefEssential은 일반 Python 메서드로 HTTP 요청을 처리하는 동기식 웹 프레임워크입니다.
12
+ `async` / `await` 없이 라우트, 요청 검증, 응답, 확장 기능을 구성할 수 있습니다.
13
+
14
+ - **동기식 요청 처리**: `RequestRunner` 클래스로 HTTP 메서드와 경로, 핸들러를 선언합니다.
15
+ - **요청 검증**: 경로·쿼리·헤더·본문 값에 검증기와 변환기를 적용합니다.
16
+ - **응답과 스트리밍**: JSON·텍스트·파일 응답, 스트리밍, Server-Sent Events, WebSocket을 지원합니다.
17
+ - **확장 기능**: 상태 확인, Ping, Metrics, OpenAPI 명세와 Swagger UI, 파일 제공 기능을 조합할 수 있습니다.
18
+ - **서버 어댑터**: 기본 Python 서버를 제공하며 Gunicorn과 Uvicorn 어댑터를 선택적으로 설치할 수 있습니다.
19
+ - **생명주기 훅**: 애플리케이션, 확장, 요청 실행 단계에 훅을 등록할 수 있습니다.
20
+
21
+ ## 요구 사항 및 설치
22
+
23
+ - Python 3.9 이상
24
+
25
+ ```bash
26
+ pip install DefEssential
27
+ ```
28
+
29
+ 기본 Python 서버는 추가 서버 의존성 없이 사용할 수 있습니다. 선택적 어댑터가 필요한 경우 해당 추가 기능을 설치합니다.
30
+
31
+ ```bash
32
+ pip install "DefEssential[Gunicorn]"
33
+ pip install "DefEssential[Uvicorn]"
34
+ ```
35
+
36
+ ## 빠른 시작
37
+
38
+ ```python
39
+ from DefEssential import Application, Request, RequestRunner
40
+ from DefEssential.Adapter.Implements.Python import Server
41
+ from DefEssential.Responses import ResponseText
42
+
43
+
44
+ class Hello(RequestRunner, method='GET', url='/hello'):
45
+ def run(self, request: Request):
46
+ return ResponseText('Hello, world!')
47
+
48
+
49
+ application = Application()
50
+ application.add(Hello)
51
+
52
+ Server('127.0.0.1', 8000, application).run()
53
+ ```
54
+
55
+ 서버를 실행한 뒤 `GET /hello` 요청은 `Hello, world!`를 반환합니다. 각 요청은 새로운 핸들러 인스턴스에서 처리됩니다.
56
+
57
+ ## 요청 핸들러와 검증
58
+
59
+ `RequestRunner` 하위 클래스 선언에서 `method`와 `url`을 지정하고 `run()` 메서드에 동기식 처리 로직을 작성합니다. URL 경로 변수는 `:name` 형식으로 선언하며, `parameters`, `qs`, `headers`, `body` 설정에 검증 규칙을 지정할 수 있습니다.
60
+
61
+ ```python
62
+ from DefEssential import (
63
+ Parameters,
64
+ Request,
65
+ RequestRunner,
66
+ )
67
+ from DefEssential.Responses import ResponseJSON
68
+ from DefEssential.Validator.Patterns import (
69
+ IsGreaterThan,
70
+ ToInteger,
71
+ )
72
+
73
+
74
+ class ReadValue(
75
+ RequestRunner,
76
+ method='GET',
77
+ url='/values/:value',
78
+ parameters=Parameters({
79
+ 'value': ToInteger(IsGreaterThan(0)),
80
+ }),
81
+ ):
82
+ def run(self, request: Request):
83
+ return ResponseJSON({'value': request.parameters['value']})
84
+ ```
85
+
86
+ 검증기와 변환기는 요청 입력을 핸들러에 전달하기 전에 실행됩니다. 본문은 `Body`와 콘텐츠 리더를 통해 JSON, 텍스트, 바이너리 또는 스트리밍 방식으로 읽을 수 있습니다.
87
+
88
+ ## 응답, 스트리밍 및 프로토콜
89
+
90
+ - `ResponseJSON`, `ResponseText`, `ResponseFile`로 JSON, 텍스트, 파일 응답을 만들 수 있습니다.
91
+ - `request.writer`와 스트림 writer로 응답 본문을 직접 스트리밍할 수 있습니다. 이 방식에서는 핸들러가 응답을 직접 기록하고 `None`을 반환합니다.
92
+ - Server-Sent Events writer로 이벤트 스트림을 전송할 수 있습니다.
93
+ - `WebSocket` API는 HTTP 연결을 업그레이드한 뒤 text 및 binary 메시지를 송수신합니다.
94
+
95
+ 직접 출력 및 스트리밍 응답은 일반 반환 응답과 처리 시점이 다릅니다. 관련 훅 계약과 전송 동작에 맞춰 응답 처리를 구성하세요.
96
+
97
+ ## 확장 기능과 서버
98
+
99
+ 확장 기능은 `Application(exts=...)`에 전달해 사용합니다. 기본 제공 확장은 다음과 같습니다.
100
+
101
+ - `Status`: 상태 페이지 제공
102
+ - `Ping`: 애플리케이션 실행 상태 응답
103
+ - `Metrics`: JSON 또는 Prometheus 형식의 요청 통계 제공
104
+ - `Specifications`: 등록된 라우트로부터 OpenAPI 3.1 문서 생성
105
+ - `Swagger`: Swagger UI 제공
106
+ - `File`, `FileSystem`: 고정 파일 또는 파일 시스템 콘텐츠 제공
107
+
108
+ 기본 서버는 `DefEssential.Adapter.Implements.Python.Server`입니다. Gunicorn과 Uvicorn 어댑터는 각각 `DefEssential[Gunicorn]`, `DefEssential[Uvicorn]` 추가 기능으로 설치합니다. 어댑터와 worker 방식에 따라 지원 환경과 전송 특성이 다를 수 있습니다.
109
+
110
+ ## 생명주기 훅과 로깅
111
+
112
+ `Application`은 `on_startup`, `on_cleanup`, `on_request`, `on_request_response`, `on_request_error` 훅을 받을 수 있습니다. `Extension`은 시작·정리·요청·응답·오류 훅을, `RequestRunner`는 요청·응답·오류 훅을 지원합니다.
113
+
114
+ DefEssential은 표준 `logging` 로거를 사용하며 애플리케이션의 핸들러나 로그 레벨을 대신 설정하지 않습니다. 애플리케이션에서 필요한 로거와 출력 정책을 구성하세요.
115
+
116
+ ## 샘플 및 테스트
117
+
118
+ 저장소의 `samples/Sample.py`에는 요청, 검증, 인증, 파일, 스트리밍, WebSocket 및 기본 제공 확장을 포함한 실행 예제가 있습니다. 프로젝트 루트에서 다음 명령으로 샘플 서버를 실행할 수 있습니다.
119
+
120
+ ```bash
121
+ python samples/Sample.py
122
+ ```
123
+
124
+ 저장소 코드를 테스트하려면 프로젝트 루트에서 editable 설치를 한 뒤 다음 명령을 실행합니다.
125
+
126
+ ```bash
127
+ pip install -e .
128
+ python -m unittest discover -s tests
129
+ ```
130
+
131
+ ## 라이선스
132
+
133
+ 라이선스 조건은 저장소의 `LICENSE` 파일을 확인하세요.
@@ -0,0 +1,48 @@
1
+ # DefEssential
2
+
3
+ DefEssential is a synchronous Python web application framework for handling HTTP requests with ordinary Python methods. It provides route declarations, request validation, response helpers, streaming APIs, lifecycle hooks, and optional extensions without requiring `async` / `await`.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.9 or later
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install DefEssential
13
+ ```
14
+
15
+ The package includes a built-in Python HTTP server. Optional extras provide additional server adapters:
16
+
17
+ ```bash
18
+ pip install "DefEssential[Gunicorn]"
19
+ pip install "DefEssential[Uvicorn]"
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```python
25
+ from DefEssential import Application, Request, RequestRunner
26
+ from DefEssential.Adapter.Implements.Python import Server
27
+ from DefEssential.Responses import ResponseText
28
+
29
+
30
+ class Hello(RequestRunner, method='GET', url='/hello'):
31
+ def run(self, request: Request):
32
+ return ResponseText('Hello, world!')
33
+
34
+
35
+ application = Application()
36
+ application.add(Hello)
37
+ Server('127.0.0.1', 8000, application).run()
38
+ ```
39
+
40
+ The handler declares an HTTP method and path on its `RequestRunner` class. The application registers the handler, and the server runs the application. A `GET /hello` request returns a plain-text response.
41
+
42
+ ## Request handling
43
+
44
+ Define handlers as `RequestRunner` subclasses and implement `run()` with synchronous Python code. Configure path, query, header, and body validation in the class declaration. The framework also provides JSON, text, and file responses, direct streaming, Server-Sent Events, WebSocket support, and extensions for status checks, metrics, OpenAPI specifications, Swagger UI, and file serving.
45
+
46
+ ## License
47
+
48
+ See the `LICENSE` file included with the source distribution for license terms.
@@ -0,0 +1,118 @@
1
+ # 로깅 지원 구현 기록
2
+
3
+ ## 1. 개요
4
+
5
+ [로깅 지원 이슈](./ISSUE.md)와 [요구사항 명세서](./REQUIREMENTS.md)에 따라 프레임워크 로그를 `DefEssential` 계층으로 정리하고 주요 실행 경로의 로그를 보강했다.
6
+
7
+ DefStream처럼 Python 표준 `logging`으로 로그를 기록하며, 출력 설정은 샘플의 Application 시작 훅에서 담당한다. 이 문서는 2026-10-25 기준 최종 구현과 앞선 구현 작업에서 확인한 검증 결과를 기록한다.
8
+
9
+ ## 2. 최종 구현 방식
10
+
11
+ - 클래스는 `logger = getLogger('DefEssential.…')`로 고정 이름을 선언하고 메서드에서 직접 사용한다.
12
+ - 공통 함수는 담당 경계의 모듈 로거를 사용한다. 로거를 전달하기 위한 공개 인자는 추가하지 않았다.
13
+ - 사용자 하위 클래스는 기존 로거를 상속하며 필요하면 명시적으로 재정의할 수 있다.
14
+ - 라이브러리는 핸들러·포매터·필터·레벨을 설정하거나 기존 사용자 설정을 제거하지 않는다. `basicConfig()`와 `NullHandler`도 추가하지 않았다.
15
+ - 로깅 때문에 공개 훅 계약이나 요청·응답 처리 순서를 바꾸지 않는다.
16
+
17
+ ### 2.1. 후속 검토에 따른 단순화
18
+
19
+ 초기 구현의 로깅 전용 구조를 다음과 같이 제거했다.
20
+
21
+ - 단계 로그용 `stage()` context manager와 자동 단계별 타이머를 제거하고 처리 지점의 직접 logger 호출로 바꿨다.
22
+ - 라우트·어댑터 전달용 `REQUEST_ROUTE`, `SERVER_ADAPTER` ContextVar와 설정·해제 코드를 제거했다.
23
+ - 클래스명·문자열 처리 함수도 표준 Python 속성과 표현으로 대체하고 별도 로깅 유틸 파일을 삭제했다.
24
+
25
+ 라우트는 Application의 등록·선택 지점에서, 어댑터는 각 서버의 생명주기 지점에서 기록한다. 요청 실행 단계와 HTTP 전송 결과는 지역 변수로 관리하며 로깅용 공유 상태를 두지 않는다.
26
+
27
+ ## 3. 기록 범위와 레벨
28
+
29
+ | 대상 | 로거 | 구현 내용 |
30
+ | --- | --- | --- |
31
+ | [Application](../../../src/Application.py) | `DefEssential.Application` | 시작·종료, 확장 연결·롤백·정리, 라우트 등록·선택, 훅 실행과 디스패치 |
32
+ | [요청 실행기](../../../src/Runners/RunRequest.py) | `DefEssential.RequestRunner` | 인증·검증·본문 처리·핸들러 생성·실행, 요청·응답·오류 훅과 직접 출력 단계 |
33
+ | [확장 실행기](../../../src/Runners/RunExtension.py) | `DefEssential.Extension` | 확장 호출과 요청·응답·오류 훅 |
34
+ | [공통 요청 처리기](../../../src/Adapter/RequestHandler.py) | `DefEssential.Server` | HTTP 수신, 오류 응답 선택, 최종 전송 결과와 본문 정리 실패 |
35
+ | [Python](../../../src/Adapter/Implements/Python/Server.py)·[Gunicorn](../../../src/Adapter/Implements/Gunicorn/Server.py)·[Uvicorn](../../../src/Adapter/Implements/Uvicorn/Server.py) 서버 | `DefEssential.Server` | 어댑터 식별, 실제 바인딩 후 준비 완료, 시작·종료·실패 |
36
+ | [WebSocket](../../../src/IO/WebSocket/WebSocket.py) | `DefEssential.IO.WebSocket` | 연결 상태, 종료 코드, 프로토콜 위반과 내부 실패 |
37
+ | [내장 확장](../../../src/Extensions) | `DefEssential.Extensions.*` | File·FileSystem·Ping·Metrics·Status·Specifications·Swagger의 주요 처리와 실패 |
38
+
39
+ | 레벨 | 기록 기준 |
40
+ | --- | --- |
41
+ | DEBUG | 등록·선택, 검증·실행·훅의 진입과 완료, 정상 분기 및 관찰 정보 |
42
+ | INFO | 애플리케이션·서버 생명주기, HTTP 전송 결과 요약 |
43
+ | WARNING | 클라이언트 연결 단절, WebSocket 프로토콜 위반 |
44
+ | ERROR | 예상하지 못한 내부 오류, 시작·전송·정리 실패 |
45
+
46
+ 일반적인 4xx 응답이라는 이유만으로 WARNING이나 ERROR로 승격하지 않는다. 실패한 단계 뒤에 정상 완료 로그를 남기지 않으며, 샘플의 훅 관찰 로그는 DEBUG로 구분한다.
47
+
48
+ ## 4. 요청 처리와 오류 경계
49
+
50
+ ### 4.1. 단계 로그
51
+
52
+ [RunRequest](../../../src/Runners/RunRequest.py)는 기존 실행 흐름 안에서 직접 로그를 기록한다. 지역 `phase` 값으로 실패 단계를 식별하고, 실제 호출이 끝난 뒤에만 완료를 기록한다. 로깅만을 위한 별도 실행 래퍼는 두지 않았다.
53
+
54
+ ### 4.2. HTTP 전송 결과
55
+
56
+ [공통 요청 처리기](../../../src/Adapter/RequestHandler.py)는 요청별 전송 요약을 INFO로 한 번 기록한다.
57
+
58
+ | 항목 | 의미 |
59
+ | --- | --- |
60
+ | `request_id` | 서버 경계에서 생성한 최초 요청의 ID. 생성 전 실패하면 `None` |
61
+ | `method` | 앞 32자를 `ascii()`로 이스케이프한 메서드. 요청 생성 전에는 `None` |
62
+ | `status` | writer가 실제 응답을 시작한 경우의 상태. 시작 전에는 `None` |
63
+ | `outcome` | `completed`, `failed`, `disconnected` |
64
+ | `duration` | 공통 처리 진입부터 전송 결과 기록 직전까지 `monotonic()`으로 측정한 초 단위 시간 |
65
+
66
+ 요약에는 원시 요청 경로·쿼리, 라우트나 어댑터 전달용 상태를 추가하지 않았다.
67
+
68
+ - 반환 응답 전송 또는 직접 출력 종료를 확인한 경우에만 `completed`로 기록한다.
69
+ - 쓰기·flush·iterable 소비 실패는 성공으로 처리하지 않는다. 연결 단절은 `disconnected`로 구분한다.
70
+ - 핸들러·응답 훅 완료는 실제 전송 완료가 아니다. 101 업그레이드 역시 WebSocket 세션 전체의 성공을 뜻하지 않는다.
71
+ - 전송 요약 뒤의 본문 정리 실패는 별도 ERROR로 기록한다. 전송 시간에는 이 후속 정리 시간이 포함되지 않는다.
72
+ - Python 기본 HTTP 처리기의 헤더 생성 시점 접근 로그는 DEBUG로 구분해 INFO 요약과 중복되지 않게 했다.
73
+
74
+ ### 4.3. 예외 처리
75
+
76
+ - 프레임워크의 HTTP 오류는 기존 오류 응답으로 변환한다.
77
+ - 응답 시작 전 예상하지 못한 예외는 최종 서버 경계에서 ERROR와 traceback을 기록하고 기존 500 응답으로 처리한다.
78
+ - 응답 시작 후 실패는 두 번째 오류 응답을 보내지 않고 기록 후 전파한다.
79
+ - 연결 단절은 WARNING으로 기록하며 예외 문자열과 traceback을 반복 출력하지 않는다.
80
+ - 중간 실행 계층과 샘플 훅은 같은 전파 예외의 traceback을 중복 기록하지 않는다. 독립적인 정리 실패는 별개 사건으로 기록한다.
81
+
82
+ ## 5. 샘플 출력 설정과 기록 제한
83
+
84
+ [샘플](../../../samples/Sample.py)의 `LoggingSetup`을 첫 Application 시작 훅으로 등록했다.
85
+
86
+ - 루트 레벨을 DEBUG로 설정하고 이름이 `Sample`인 콘솔 핸들러를 추가한다.
87
+ - 핸들러의 출처 필터로 `Sample`·`Sample.*`, `DefEssential`·`DefEssential.*`만 선택한다. 유사 접두 이름과 외부 서버 로거는 제외한다.
88
+ - 같은 이름의 핸들러는 재시작 시 중복 추가하지 않으며 기존 사용자 핸들러는 제거하지 않는다. 다른 핸들러의 출력까지 제한하지는 않는다.
89
+ - 현재 포맷은 `%(asctime)s %(levelname)-8s %(name)s %(message)s`이며 밀리초와 레벨별 색상을 표시한다. 색상 포매터는 LogRecord 복사본을 사용한다.
90
+ - 시작 훅 이전의 구성·등록 로그를 소급 출력하지 않으며 기존 시작 배너는 유지한다.
91
+ - [일반 요청](../../../samples/runners/Requests.py)·[스트리밍](../../../samples/runners/Streaming.py) 샘플의 훅 로그는 DEBUG로 기록한다.
92
+
93
+ 기본 문맥에서 원시 URI·경로·query, 인증정보, 요청·응답 본문, WebSocket payload·close reason을 제외했다. Application은 선택된 등록 라우트의 앞 256자를 `ascii()`로 이스케이프해 기록한다. 이 길이는 이스케이프 전 문자 수 기준이다.
94
+
95
+ traceback에는 예외 메시지와 소스 줄이 포함될 수 있으므로 완전한 비밀정보 차단을 보장하지 않는다. 사용자 예외 메시지와 등록 라우트에 민감정보를 넣지 않아야 하며, 추가 필터링은 애플리케이션 설정의 책임이다.
96
+
97
+ ## 6. 검증 기록
98
+
99
+ 아래는 최종 코드 단순화 후 수행한 검증 결과다. 이번 문서 작성 중 테스트를 다시 실행한 결과는 아니다.
100
+
101
+ - Windows의 Python 3.12에서 작업 트리 소스를 직접 로드해 전체 `unittest` 발견 테스트를 실행했다.
102
+ - 총 **243개 중 238개 통과, 5개 건너뜀**을 확인했다. 설치된 별도 패키지를 대상으로 한 결과가 아니다.
103
+ - [로깅 전용 테스트](../../../tests/test_logging.py) 21개로 로거 상속·재정의, 설정 비간섭, 단계·레벨, 전송 요약, 실패·단절·정리, 병렬·중첩 요청, 민감정보 제외, 샘플 설정의 중복 방지를 검증했다.
104
+ - 기존 훅·writer·스트리밍·SSE·WebSocket 회귀와 Python·Uvicorn의 실제 HTTP 전송을 포함했다.
105
+ - 포트 0의 실제 준비 완료 로그, Uvicorn의 실제 바인딩 실패, Python 바인딩 실패의 테스트 더블 검증을 포함했다.
106
+ - 소스·샘플·테스트의 Python 3.9 구문 분석과 `git diff --check`를 통과했다. 주요 변경 파일의 Pylance 타입 오류도 확인했다.
107
+ - 로깅 유틸 파일 삭제와 잔여 import·호출 제거를 확인했다.
108
+
109
+ ### 환경 제약
110
+
111
+ - 건너뛴 5개는 POSIX가 필요한 Gunicorn 실행·전송 적합성 테스트다. 실제 Gunicorn 실행은 Linux·WSL 또는 CI에서 추가 확인해야 한다.
112
+ - Python 3.9는 구문 분석만 수행했다. 해당 버전의 실제 런타임 테스트를 통과한 것으로 간주하지 않는다.
113
+ - 라이브러리 미설정 시 표준 `logging.lastResort` 동작을 유지한다. Gunicorn·Uvicorn 자체 로깅 설정과 외부 접근 로그의 출력 정책은 별도로 관리해야 한다.
114
+
115
+ ## 7. 관련 문서
116
+
117
+ - [요구사항 명세서](./REQUIREMENTS.md)
118
+ - [변경 로그](./README.md)
@@ -0,0 +1,9 @@
1
+ # 로깅 지원
2
+
3
+ ### 해결하려는 문제
4
+
5
+ 파편화된 로깅 정리
6
+
7
+ ### 제안하는 해결 방법
8
+
9
+ DefEssential 을 Master 키 로 하여 로그 이름을 설정하고, 부족한 로깅들을 풍부하게 추가