DefEssential 0.1.6__19-py3-none-any.whl

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 (104) hide show
  1. DefEssential/Adapter/Implements/Gunicorn/Server.py +134 -0
  2. DefEssential/Adapter/Implements/Gunicorn/Transport.py +171 -0
  3. DefEssential/Adapter/Implements/Gunicorn/__init__.py +5 -0
  4. DefEssential/Adapter/Implements/Python/Server.py +245 -0
  5. DefEssential/Adapter/Implements/Python/__init__.py +5 -0
  6. DefEssential/Adapter/Implements/Uvicorn/HTTP.py +256 -0
  7. DefEssential/Adapter/Implements/Uvicorn/Portal.py +65 -0
  8. DefEssential/Adapter/Implements/Uvicorn/Server.py +102 -0
  9. DefEssential/Adapter/Implements/Uvicorn/WebSocket.py +220 -0
  10. DefEssential/Adapter/Implements/Uvicorn/__init__.py +5 -0
  11. DefEssential/Adapter/Implements/__init__.py +3 -0
  12. DefEssential/Adapter/RequestHandler.py +70 -0
  13. DefEssential/Adapter/Server.py +54 -0
  14. DefEssential/Adapter/WSGI.py +219 -0
  15. DefEssential/Adapter/__init__.py +5 -0
  16. DefEssential/Application.py +579 -0
  17. DefEssential/Body.py +50 -0
  18. DefEssential/Content.py +18 -0
  19. DefEssential/Credentials.py +245 -0
  20. DefEssential/CrossOriginResourceSharing.py +107 -0
  21. DefEssential/Descriptor/DataObject.py +409 -0
  22. DefEssential/Descriptor/Types.py +248 -0
  23. DefEssential/Descriptor/Value.py +188 -0
  24. DefEssential/Descriptor/__init__.py +172 -0
  25. DefEssential/Error.py +66 -0
  26. DefEssential/Extension.py +184 -0
  27. DefEssential/Extensions/File.py +89 -0
  28. DefEssential/Extensions/FileSystem.py +184 -0
  29. DefEssential/Extensions/Metrics.py +500 -0
  30. DefEssential/Extensions/Ping.py +89 -0
  31. DefEssential/Extensions/Specifications.py +328 -0
  32. DefEssential/Extensions/Status.html +207 -0
  33. DefEssential/Extensions/Status.py +104 -0
  34. DefEssential/Extensions/Swagger.html +77 -0
  35. DefEssential/Extensions/Swagger.py +111 -0
  36. DefEssential/Extensions/Utils/Routes.py +42 -0
  37. DefEssential/Extensions/Utils/__init__.py +43 -0
  38. DefEssential/Extensions/__init__.py +21 -0
  39. DefEssential/IO/Readers/ChunkedStream.py +106 -0
  40. DefEssential/IO/Readers/ContentReader.py +36 -0
  41. DefEssential/IO/Readers/ContentReaders.py +241 -0
  42. DefEssential/IO/Readers/__init__.py +25 -0
  43. DefEssential/IO/RequestReader.py +125 -0
  44. DefEssential/IO/ResponseWriter.py +241 -0
  45. DefEssential/IO/WebSocket/WebSocket.py +282 -0
  46. DefEssential/IO/WebSocket/WebSocketError.py +17 -0
  47. DefEssential/IO/WebSocket/WebSocketFrame.py +68 -0
  48. DefEssential/IO/WebSocket/WebSocketFrameReader.py +98 -0
  49. DefEssential/IO/WebSocket/WebSocketHandshake.py +94 -0
  50. DefEssential/IO/WebSocket/__init__.py +13 -0
  51. DefEssential/IO/Writers/ChunkedStreamWriter.py +19 -0
  52. DefEssential/IO/Writers/ServerSentEvents.py +41 -0
  53. DefEssential/IO/Writers/ServerSentEventsWriter.py +67 -0
  54. DefEssential/IO/Writers/StreamWriter.py +57 -0
  55. DefEssential/IO/Writers/__init__.py +11 -0
  56. DefEssential/IO/__init__.py +7 -0
  57. DefEssential/Request.py +110 -0
  58. DefEssential/RequestRunner.py +261 -0
  59. DefEssential/Response.py +87 -0
  60. DefEssential/ResponseSpecification.py +23 -0
  61. DefEssential/Responses/ResponseFile.py +108 -0
  62. DefEssential/Responses/__init__.py +63 -0
  63. DefEssential/Route.py +55 -0
  64. DefEssential/Router.py +48 -0
  65. DefEssential/Runners/RunExtension.py +101 -0
  66. DefEssential/Runners/RunFactory.py +26 -0
  67. DefEssential/Runners/RunOptions.py +77 -0
  68. DefEssential/Runners/RunRequest.py +121 -0
  69. DefEssential/Runners/__init__.py +5 -0
  70. DefEssential/Startup.py +104 -0
  71. DefEssential/Utils/FileSystemObject/Configuration.py +15 -0
  72. DefEssential/Utils/FileSystemObject/Connection.py +57 -0
  73. DefEssential/Utils/FileSystemObject/File.py +41 -0
  74. DefEssential/Utils/FileSystemObject/Implements/FileSystem/Configuration.py +15 -0
  75. DefEssential/Utils/FileSystemObject/Implements/FileSystem/Connection.py +115 -0
  76. DefEssential/Utils/FileSystemObject/Implements/FileSystem/File.py +77 -0
  77. DefEssential/Utils/FileSystemObject/Implements/FileSystem/__init__.py +11 -0
  78. DefEssential/Utils/FileSystemObject/Implements/__init__.py +3 -0
  79. DefEssential/Utils/FileSystemObject/__init__.py +11 -0
  80. DefEssential/Utils/Loader.py +81 -0
  81. DefEssential/Utils/__init__.py +3 -0
  82. DefEssential/Validator/Pattern.py +36 -0
  83. DefEssential/Validator/Patterns/Array.py +30 -0
  84. DefEssential/Validator/Patterns/Common.py +92 -0
  85. DefEssential/Validator/Patterns/Compare.py +180 -0
  86. DefEssential/Validator/Patterns/Condition.py +60 -0
  87. DefEssential/Validator/Patterns/DataObject.py +85 -0
  88. DefEssential/Validator/Patterns/DateTime.py +111 -0
  89. DefEssential/Validator/Patterns/Number.py +43 -0
  90. DefEssential/Validator/Patterns/Object.py +150 -0
  91. DefEssential/Validator/Patterns/Size.py +110 -0
  92. DefEssential/Validator/Patterns/String.py +85 -0
  93. DefEssential/Validator/Patterns/Type.py +350 -0
  94. DefEssential/Validator/Patterns/__init__.py +137 -0
  95. DefEssential/Validator/Validate.py +29 -0
  96. DefEssential/Validator/Validator.py +30 -0
  97. DefEssential/Validator/__init__.py +11 -0
  98. DefEssential/Validators.py +255 -0
  99. DefEssential/__init__.py +83 -0
  100. defessential-0.1.6.dist-info/METADATA +41 -0
  101. defessential-0.1.6.dist-info/RECORD +104 -0
  102. defessential-0.1.6.dist-info/WHEEL +6 -0
  103. defessential-0.1.6.dist-info/licenses/LICENSE +674 -0
  104. defessential-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,54 @@
1
+ from abc import (
2
+ ABC,
3
+ abstractmethod,
4
+ )
5
+ from types import TracebackType
6
+ from typing import (
7
+ Optional,
8
+ TypeVar,
9
+ )
10
+
11
+ from ..Application import Application
12
+
13
+ __all__ = (
14
+ 'Server',
15
+ )
16
+
17
+
18
+ ServerType = TypeVar(
19
+ 'ServerType',
20
+ bound='Server',
21
+ )
22
+
23
+
24
+ class Server(ABC):
25
+ """애플리케이션을 지정한 주소에서 실행하는 서버 어댑터의 계약."""
26
+
27
+ def __init__(
28
+ self,
29
+ host: str,
30
+ port: int,
31
+ application: Application,
32
+ ):
33
+ self.host = host
34
+ self.port = port
35
+ self.application = application
36
+ return
37
+
38
+ def __enter__(self: ServerType) -> ServerType:
39
+ """서버를 시작하지 않고 같은 인스턴스를 반환한다."""
40
+ return self
41
+
42
+ def __exit__(
43
+ self,
44
+ exception_type: Optional[type[BaseException]],
45
+ exception: Optional[BaseException],
46
+ traceback: Optional[TracebackType],
47
+ ) -> None:
48
+ """예외를 전파한다. 실행 중 자원은 run()에서 정리한다."""
49
+ return
50
+
51
+ @abstractmethod
52
+ def run(self) -> None:
53
+ """서버 종료까지 실행하고 애플리케이션 생명주기를 관리한다."""
54
+ raise NotImplementedError
@@ -0,0 +1,219 @@
1
+ from typing import (
2
+ Any,
3
+ Callable,
4
+ Iterable,
5
+ Mapping,
6
+ Optional,
7
+ Protocol,
8
+ )
9
+ from urllib.parse import quote_from_bytes
10
+ from wsgiref.util import is_hop_by_hop
11
+
12
+ from ..Application import Application
13
+ from ..Error import Error
14
+ from ..IO.RequestReader import RequestReader
15
+ from ..IO.ResponseWriter import ResponseWriter
16
+ from ..Request import Request
17
+ from ..Response import Response
18
+ from .RequestHandler import handle_request
19
+
20
+ __all__ = (
21
+ 'StartResponse',
22
+ 'WSGITransport',
23
+ 'WSGIResponseWriter',
24
+ 'WSGIApplication',
25
+ )
26
+
27
+
28
+ class StartResponse(Protocol):
29
+ def __call__(
30
+ self,
31
+ status: str,
32
+ headers: list[tuple[str, str]],
33
+ /,
34
+ ) -> Callable[[bytes], object]:
35
+ ...
36
+
37
+
38
+ class WSGITransport:
39
+ """HTTP 프레이밍을 WSGI 서버에 맡기고 바이트를 즉시 전달한다."""
40
+
41
+ def __init__(
42
+ self,
43
+ start_response: StartResponse,
44
+ ):
45
+ self.start_response = start_response
46
+ self.output: Optional[Callable[[bytes], object]] = None
47
+ return
48
+
49
+ def begin(
50
+ self,
51
+ response: Response,
52
+ chunked: bool,
53
+ ) -> None:
54
+ headers = [
55
+ (
56
+ key,
57
+ value,
58
+ ) for key, value in response.headers()
59
+ if not is_hop_by_hop(key)
60
+ and not (key.lower() == 'content-length' and response.status in (
61
+ 204,
62
+ 205,
63
+ 304,
64
+ ))
65
+ ]
66
+ if response.status == 205:
67
+ headers.append((
68
+ 'Content-Length',
69
+ '0',
70
+ ))
71
+ self.output = self.start_response(
72
+ f'{response.status} {response.message}',
73
+ headers,
74
+ )
75
+ return
76
+
77
+ def write(
78
+ self,
79
+ data: bytes,
80
+ /,
81
+ ) -> int:
82
+ if self.output is None:
83
+ raise RuntimeError('WSGI response must be started before writing')
84
+ self.output(data)
85
+ return len(data)
86
+
87
+ def flush(self) -> None:
88
+ # WSGI의 write() 호출 자체가 서버로 바이트를 전달한다.
89
+ return
90
+
91
+
92
+ class WSGIResponseWriter(ResponseWriter):
93
+ """직접 출력 API를 유지하면서 청크 프레이밍의 중복 적용을 방지한다."""
94
+
95
+ def start(
96
+ self,
97
+ response: Response,
98
+ chunked: bool,
99
+ ) -> None:
100
+ super().start(
101
+ response,
102
+ False,
103
+ )
104
+ return
105
+
106
+ def upgrade(
107
+ self,
108
+ response: Response,
109
+ ) -> None:
110
+ raise Error(
111
+ 501,
112
+ 'WSGI adapters do not support protocol upgrades',
113
+ )
114
+
115
+
116
+ class WSGIApplication:
117
+ """DefEssential 요청을 Gunicorn과 a2wsgi의 WSGI 호출에 연결한다."""
118
+
119
+ def __init__(
120
+ self,
121
+ application: Application,
122
+ ):
123
+ self.application = application
124
+ return
125
+
126
+ def request_reader(
127
+ self,
128
+ environ: Mapping[str, Any],
129
+ ) -> RequestReader:
130
+ length = environ.get('CONTENT_LENGTH', '')
131
+ transfer = environ.get('HTTP_TRANSFER_ENCODING', '')
132
+ if length and (not length.isascii() or not length.isdigit()):
133
+ raise Error(
134
+ 400,
135
+ 'Invalid Content-Length',
136
+ )
137
+ if transfer and (transfer.lower() != 'chunked' or length):
138
+ raise Error(
139
+ 400,
140
+ 'Ambiguous or unsupported request body framing',
141
+ )
142
+ return RequestReader(
143
+ environ['wsgi.input'],
144
+ size=int(length) if length else None,
145
+ chunked=transfer.lower() == 'chunked',
146
+ decoded=True,
147
+ )
148
+
149
+ def response_writer(
150
+ self,
151
+ environ: Mapping[str, Any],
152
+ start_response: StartResponse,
153
+ ) -> ResponseWriter:
154
+ transport = WSGITransport(start_response)
155
+ return WSGIResponseWriter(
156
+ transport.begin,
157
+ transport,
158
+ method=environ['REQUEST_METHOD'],
159
+ version=environ['SERVER_PROTOCOL'],
160
+ )
161
+
162
+ def __call__(
163
+ self,
164
+ environ: Mapping[str, Any],
165
+ start_response: StartResponse,
166
+ ) -> Iterable[bytes]:
167
+ writer = self.response_writer(
168
+ environ,
169
+ start_response,
170
+ )
171
+
172
+ def create_request() -> Request:
173
+ reader = self.request_reader(environ)
174
+ headers = {
175
+ key[5:].replace('_', '-'): value
176
+ for key, value in environ.items()
177
+ if key.startswith('HTTP_')
178
+ }
179
+ for source, target in (
180
+ (
181
+ 'CONTENT_LENGTH',
182
+ 'Content-Length',
183
+ ),
184
+ (
185
+ 'CONTENT_TYPE',
186
+ 'Content-Type',
187
+ ),
188
+ ):
189
+ if environ.get(source):
190
+ headers[target] = environ[source]
191
+ uri = environ.get('RAW_URI') or environ.get('REQUEST_URI')
192
+ if uri is None:
193
+ raw_path = environ.get('asgi.scope', {}).get('raw_path')
194
+ if raw_path is not None:
195
+ uri = raw_path.decode('latin-1')
196
+ else:
197
+ path = environ.get('SCRIPT_NAME', '') + environ['PATH_INFO']
198
+ uri = quote_from_bytes(
199
+ path.encode('latin-1'),
200
+ safe="/:@!$&'()*+,;=-._~",
201
+ )
202
+ if environ.get('QUERY_STRING'):
203
+ uri += '?' + environ['QUERY_STRING']
204
+ return Request(
205
+ address=environ.get('REMOTE_ADDR', ''),
206
+ port=int(environ.get('REMOTE_PORT', 0)),
207
+ method=environ['REQUEST_METHOD'],
208
+ uri=uri,
209
+ headers=headers,
210
+ reader=reader,
211
+ writer=writer,
212
+ )
213
+
214
+ handle_request(
215
+ self.application,
216
+ create_request,
217
+ writer,
218
+ )
219
+ return ()
@@ -0,0 +1,5 @@
1
+ from .Server import Server
2
+
3
+ __all__ = (
4
+ 'Server',
5
+ )