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.
- DefEssential/Adapter/Implements/Gunicorn/Server.py +134 -0
- DefEssential/Adapter/Implements/Gunicorn/Transport.py +171 -0
- DefEssential/Adapter/Implements/Gunicorn/__init__.py +5 -0
- DefEssential/Adapter/Implements/Python/Server.py +245 -0
- DefEssential/Adapter/Implements/Python/__init__.py +5 -0
- DefEssential/Adapter/Implements/Uvicorn/HTTP.py +256 -0
- DefEssential/Adapter/Implements/Uvicorn/Portal.py +65 -0
- DefEssential/Adapter/Implements/Uvicorn/Server.py +102 -0
- DefEssential/Adapter/Implements/Uvicorn/WebSocket.py +220 -0
- DefEssential/Adapter/Implements/Uvicorn/__init__.py +5 -0
- DefEssential/Adapter/Implements/__init__.py +3 -0
- DefEssential/Adapter/RequestHandler.py +70 -0
- DefEssential/Adapter/Server.py +54 -0
- DefEssential/Adapter/WSGI.py +219 -0
- DefEssential/Adapter/__init__.py +5 -0
- DefEssential/Application.py +579 -0
- DefEssential/Body.py +50 -0
- DefEssential/Content.py +18 -0
- DefEssential/Credentials.py +245 -0
- DefEssential/CrossOriginResourceSharing.py +107 -0
- DefEssential/Descriptor/DataObject.py +409 -0
- DefEssential/Descriptor/Types.py +248 -0
- DefEssential/Descriptor/Value.py +188 -0
- DefEssential/Descriptor/__init__.py +172 -0
- DefEssential/Error.py +66 -0
- DefEssential/Extension.py +184 -0
- DefEssential/Extensions/File.py +89 -0
- DefEssential/Extensions/FileSystem.py +184 -0
- DefEssential/Extensions/Metrics.py +500 -0
- DefEssential/Extensions/Ping.py +89 -0
- DefEssential/Extensions/Specifications.py +328 -0
- DefEssential/Extensions/Status.html +207 -0
- DefEssential/Extensions/Status.py +104 -0
- DefEssential/Extensions/Swagger.html +77 -0
- DefEssential/Extensions/Swagger.py +111 -0
- DefEssential/Extensions/Utils/Routes.py +42 -0
- DefEssential/Extensions/Utils/__init__.py +43 -0
- DefEssential/Extensions/__init__.py +21 -0
- DefEssential/IO/Readers/ChunkedStream.py +106 -0
- DefEssential/IO/Readers/ContentReader.py +36 -0
- DefEssential/IO/Readers/ContentReaders.py +241 -0
- DefEssential/IO/Readers/__init__.py +25 -0
- DefEssential/IO/RequestReader.py +125 -0
- DefEssential/IO/ResponseWriter.py +241 -0
- DefEssential/IO/WebSocket/WebSocket.py +282 -0
- DefEssential/IO/WebSocket/WebSocketError.py +17 -0
- DefEssential/IO/WebSocket/WebSocketFrame.py +68 -0
- DefEssential/IO/WebSocket/WebSocketFrameReader.py +98 -0
- DefEssential/IO/WebSocket/WebSocketHandshake.py +94 -0
- DefEssential/IO/WebSocket/__init__.py +13 -0
- DefEssential/IO/Writers/ChunkedStreamWriter.py +19 -0
- DefEssential/IO/Writers/ServerSentEvents.py +41 -0
- DefEssential/IO/Writers/ServerSentEventsWriter.py +67 -0
- DefEssential/IO/Writers/StreamWriter.py +57 -0
- DefEssential/IO/Writers/__init__.py +11 -0
- DefEssential/IO/__init__.py +7 -0
- DefEssential/Request.py +110 -0
- DefEssential/RequestRunner.py +261 -0
- DefEssential/Response.py +87 -0
- DefEssential/ResponseSpecification.py +23 -0
- DefEssential/Responses/ResponseFile.py +108 -0
- DefEssential/Responses/__init__.py +63 -0
- DefEssential/Route.py +55 -0
- DefEssential/Router.py +48 -0
- DefEssential/Runners/RunExtension.py +101 -0
- DefEssential/Runners/RunFactory.py +26 -0
- DefEssential/Runners/RunOptions.py +77 -0
- DefEssential/Runners/RunRequest.py +121 -0
- DefEssential/Runners/__init__.py +5 -0
- DefEssential/Startup.py +104 -0
- DefEssential/Utils/FileSystemObject/Configuration.py +15 -0
- DefEssential/Utils/FileSystemObject/Connection.py +57 -0
- DefEssential/Utils/FileSystemObject/File.py +41 -0
- DefEssential/Utils/FileSystemObject/Implements/FileSystem/Configuration.py +15 -0
- DefEssential/Utils/FileSystemObject/Implements/FileSystem/Connection.py +115 -0
- DefEssential/Utils/FileSystemObject/Implements/FileSystem/File.py +77 -0
- DefEssential/Utils/FileSystemObject/Implements/FileSystem/__init__.py +11 -0
- DefEssential/Utils/FileSystemObject/Implements/__init__.py +3 -0
- DefEssential/Utils/FileSystemObject/__init__.py +11 -0
- DefEssential/Utils/Loader.py +81 -0
- DefEssential/Utils/__init__.py +3 -0
- DefEssential/Validator/Pattern.py +36 -0
- DefEssential/Validator/Patterns/Array.py +30 -0
- DefEssential/Validator/Patterns/Common.py +92 -0
- DefEssential/Validator/Patterns/Compare.py +180 -0
- DefEssential/Validator/Patterns/Condition.py +60 -0
- DefEssential/Validator/Patterns/DataObject.py +85 -0
- DefEssential/Validator/Patterns/DateTime.py +111 -0
- DefEssential/Validator/Patterns/Number.py +43 -0
- DefEssential/Validator/Patterns/Object.py +150 -0
- DefEssential/Validator/Patterns/Size.py +110 -0
- DefEssential/Validator/Patterns/String.py +85 -0
- DefEssential/Validator/Patterns/Type.py +350 -0
- DefEssential/Validator/Patterns/__init__.py +137 -0
- DefEssential/Validator/Validate.py +29 -0
- DefEssential/Validator/Validator.py +30 -0
- DefEssential/Validator/__init__.py +11 -0
- DefEssential/Validators.py +255 -0
- DefEssential/__init__.py +83 -0
- defessential-0.1.6.dist-info/METADATA +41 -0
- defessential-0.1.6.dist-info/RECORD +104 -0
- defessential-0.1.6.dist-info/WHEEL +6 -0
- defessential-0.1.6.dist-info/licenses/LICENSE +674 -0
- 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 ()
|