DefEssential 0.1.8__24-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 +152 -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 +273 -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 +137 -0
  9. DefEssential/Adapter/Implements/Uvicorn/WebSocket.py +222 -0
  10. DefEssential/Adapter/Implements/Uvicorn/__init__.py +5 -0
  11. DefEssential/Adapter/Implements/__init__.py +3 -0
  12. DefEssential/Adapter/RequestHandler.py +109 -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 +657 -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 +191 -0
  27. DefEssential/Extensions/File.py +95 -0
  28. DefEssential/Extensions/FileSystem.py +195 -0
  29. DefEssential/Extensions/Metrics.py +511 -0
  30. DefEssential/Extensions/Ping.py +93 -0
  31. DefEssential/Extensions/Specifications.py +342 -0
  32. DefEssential/Extensions/Status.html +207 -0
  33. DefEssential/Extensions/Status.py +110 -0
  34. DefEssential/Extensions/Swagger.html +77 -0
  35. DefEssential/Extensions/Swagger.py +117 -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 +242 -0
  45. DefEssential/IO/WebSocket/WebSocket.py +288 -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 +89 -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 +116 -0
  66. DefEssential/Runners/RunFactory.py +26 -0
  67. DefEssential/Runners/RunOptions.py +77 -0
  68. DefEssential/Runners/RunRequest.py +198 -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.8.dist-info/METADATA +86 -0
  101. defessential-0.1.8.dist-info/RECORD +104 -0
  102. defessential-0.1.8.dist-info/WHEEL +6 -0
  103. defessential-0.1.8.dist-info/licenses/LICENSE +674 -0
  104. defessential-0.1.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,152 @@
1
+ from os import name
2
+ from logging import getLogger
3
+ from typing import (
4
+ Any,
5
+ Union,
6
+ )
7
+
8
+ from ....Application import Application
9
+ from ....Startup import startup
10
+ from ...Server import Server as ServerAdapter
11
+ from .Transport import (
12
+ Body,
13
+ GunicornApplication as RequestApplication,
14
+ )
15
+
16
+ __all__ = (
17
+ 'Server',
18
+ )
19
+
20
+
21
+ class Server(ServerAdapter):
22
+ """Start and stop the application in each Gunicorn worker."""
23
+
24
+ logger = getLogger('DefEssential.Server')
25
+
26
+ def __init__(
27
+ self,
28
+ host: str,
29
+ port: int,
30
+ application: Application,
31
+ *,
32
+ workers: int = 1,
33
+ worker_class: Union[str, type] = 'sync',
34
+ **options: Any,
35
+ ):
36
+ super().__init__(
37
+ host,
38
+ port,
39
+ application,
40
+ )
41
+ if workers < 1:
42
+ raise ValueError('workers must be at least 1')
43
+ if 'bind' in options:
44
+ raise ValueError('Use host and port instead of bind')
45
+ self.workers = workers
46
+ self.worker_class = worker_class
47
+ self.options = options
48
+ return
49
+
50
+ def run(self) -> None:
51
+ if name == 'nt':
52
+ raise RuntimeError('Gunicorn requires a POSIX platform; use Linux or WSL')
53
+ # Gunicorn is an optional dependency and cannot be imported on Windows.
54
+ from gunicorn.app.base import BaseApplication
55
+
56
+ application = self.application
57
+ options = dict(self.options)
58
+ when_ready = options.pop('when_ready', None)
59
+ post_worker_init = options.pop('post_worker_init', None)
60
+ worker_exit = options.pop('worker_exit', None)
61
+ pre_request = options.pop('pre_request', None)
62
+ address = f'[{self.host}]' if ':' in self.host else self.host
63
+ options.update(
64
+ bind=f'{address}:{self.port}',
65
+ workers=self.workers,
66
+ worker_class=self.worker_class,
67
+ )
68
+
69
+ def ready(server: Any) -> None:
70
+ if when_ready is not None:
71
+ when_ready(server)
72
+ startup(
73
+ application,
74
+ 'Gunicorn',
75
+ self.host,
76
+ self.port,
77
+ )
78
+ for listener in server.LISTENERS:
79
+ self.logger.info(
80
+ 'Server ready: adapter=Gunicorn address=%s',
81
+ listener.getsockname(),
82
+ )
83
+ return
84
+
85
+ def start(worker: Any) -> None:
86
+ application.start()
87
+ if post_worker_init is not None:
88
+ post_worker_init(worker)
89
+ return
90
+
91
+ def prepare(
92
+ worker: Any,
93
+ request: Any,
94
+ ) -> None:
95
+ if pre_request is not None:
96
+ pre_request(
97
+ worker,
98
+ request,
99
+ )
100
+ request.body = Body(request)
101
+ if any(
102
+ key == 'UPGRADE' and value.lower() == 'websocket'
103
+ for key, value in request.headers
104
+ ):
105
+ request.force_close()
106
+ return
107
+
108
+ def stop(
109
+ server: Any,
110
+ worker: Any,
111
+ ) -> None:
112
+ try:
113
+ application.stop()
114
+ finally:
115
+ if worker_exit is not None:
116
+ worker_exit(
117
+ server,
118
+ worker,
119
+ )
120
+ return
121
+
122
+ options.update(
123
+ when_ready=ready,
124
+ post_worker_init=start,
125
+ worker_exit=stop,
126
+ pre_request=prepare,
127
+ )
128
+
129
+ class GunicornApplication(BaseApplication):
130
+ def load_config(self) -> None:
131
+ for key, value in options.items():
132
+ self.cfg.set(
133
+ key,
134
+ value,
135
+ )
136
+ return
137
+
138
+ def load(self) -> RequestApplication:
139
+ return RequestApplication(application)
140
+
141
+ self.logger.info('Server starting: adapter=Gunicorn host=%s port=%s', self.host, self.port)
142
+ try:
143
+ GunicornApplication().run()
144
+ except SystemExit as error:
145
+ if error.code:
146
+ self.logger.error('Server exited unsuccessfully: adapter=Gunicorn code=%s', error.code)
147
+ raise
148
+ except Exception:
149
+ self.logger.exception('Server execution failed: adapter=Gunicorn')
150
+ raise
151
+ self.logger.info('Server stopped: adapter=Gunicorn')
152
+ return
@@ -0,0 +1,171 @@
1
+ from typing import (
2
+ Any,
3
+ Mapping,
4
+ )
5
+
6
+ from ....IO.Readers.ChunkedStream import ChunkedStream
7
+ from ....IO.RequestReader import RequestReader
8
+ from ....IO.ResponseWriter import ResponseWriter
9
+ from ....Response import Response
10
+ from ...WSGI import (
11
+ StartResponse,
12
+ WSGIApplication,
13
+ WSGIResponseWriter,
14
+ WSGITransport,
15
+ )
16
+
17
+ __all__ = (
18
+ 'Input',
19
+ 'Body',
20
+ 'Transport',
21
+ 'Writer',
22
+ 'GunicornApplication',
23
+ )
24
+
25
+
26
+ class Input:
27
+ """Share the Gunicorn parser's buffered input and body state with the framework and worker."""
28
+
29
+ def __init__(
30
+ self,
31
+ request: Any,
32
+ ):
33
+ self.unreader = request.unreader
34
+ headers = dict(request.headers)
35
+ length = headers.get('CONTENT-LENGTH')
36
+ self.request_reader = RequestReader(
37
+ self,
38
+ size=int(length) if length is not None else None,
39
+ chunked=headers.get('TRANSFER-ENCODING', '').lower() == 'chunked',
40
+ )
41
+ self.body = ChunkedStream(self.request_reader) if self.request_reader.chunked else self.request_reader
42
+ return
43
+
44
+ def read(
45
+ self,
46
+ size: int = -1,
47
+ /,
48
+ ) -> bytes:
49
+ return self.unreader.read(size)
50
+
51
+ def readline(
52
+ self,
53
+ size: int = -1,
54
+ /,
55
+ ) -> bytes:
56
+ parts = []
57
+ while size < 0 or len(parts) < size:
58
+ data = self.unreader.read(1)
59
+ if not data:
60
+ break
61
+ parts.append(data)
62
+ if data == b'\n':
63
+ break
64
+ return b''.join(parts)
65
+
66
+
67
+ class Body:
68
+ """Preserve the application's read position when the worker discards the remaining body."""
69
+
70
+ def __init__(
71
+ self,
72
+ request: Any,
73
+ ):
74
+ self.input = Input(request)
75
+ return
76
+
77
+ def read(
78
+ self,
79
+ size: int = -1,
80
+ ) -> bytes:
81
+ return self.input.body.read(size)
82
+
83
+ def readline(
84
+ self,
85
+ size: int = -1,
86
+ ) -> bytes:
87
+ return self.input.body.readline(size)
88
+
89
+
90
+ class Transport(WSGITransport):
91
+ """Delegate HTTP to Gunicorn and use the raw socket after a 101 upgrade."""
92
+
93
+ def __init__(
94
+ self,
95
+ start_response: StartResponse,
96
+ socket: Any,
97
+ ):
98
+ super().__init__(start_response)
99
+ self.socket = socket
100
+ self.upgraded = False
101
+ return
102
+
103
+ def begin(
104
+ self,
105
+ response: Response,
106
+ chunked: bool,
107
+ ) -> None:
108
+ if response.status != 101:
109
+ super().begin(
110
+ response,
111
+ chunked,
112
+ )
113
+ return
114
+ output = self.start_response(
115
+ f'{response.status} {response.message}',
116
+ response.headers(),
117
+ )
118
+ output(b'')
119
+ self.upgraded = True
120
+ return
121
+
122
+ def write(
123
+ self,
124
+ data: bytes,
125
+ /,
126
+ ) -> int:
127
+ if self.upgraded:
128
+ self.socket.sendall(data)
129
+ return len(data)
130
+ return super().write(data)
131
+
132
+
133
+ class Writer(WSGIResponseWriter):
134
+ def upgrade(
135
+ self,
136
+ response: Response,
137
+ ) -> None:
138
+ ResponseWriter.upgrade(
139
+ self,
140
+ response,
141
+ )
142
+ return
143
+
144
+
145
+ class GunicornApplication(WSGIApplication):
146
+ """Provide the same I/O and WebSocket API for sync, gthread, and gevent workers."""
147
+
148
+ def request_reader(
149
+ self,
150
+ environ: Mapping[str, Any],
151
+ ) -> RequestReader:
152
+ body = environ['wsgi.input']
153
+ if not isinstance(body, Body):
154
+ raise TypeError('Gunicorn input hook was not installed')
155
+ return body.input.request_reader
156
+
157
+ def response_writer(
158
+ self,
159
+ environ: Mapping[str, Any],
160
+ start_response: StartResponse,
161
+ ) -> ResponseWriter:
162
+ transport = Transport(
163
+ start_response,
164
+ environ['gunicorn.socket'],
165
+ )
166
+ return Writer(
167
+ transport.begin,
168
+ transport,
169
+ method=environ['REQUEST_METHOD'],
170
+ version=environ['SERVER_PROTOCOL'],
171
+ )
@@ -0,0 +1,5 @@
1
+ from .Server import Server
2
+
3
+ __all__ = (
4
+ 'Server',
5
+ )
@@ -0,0 +1,273 @@
1
+ from http.server import (
2
+ BaseHTTPRequestHandler,
3
+ ThreadingHTTPServer,
4
+ )
5
+ from logging import getLogger
6
+ from typing import (
7
+ Optional,
8
+ Union,
9
+ )
10
+
11
+ from ....Application import Application
12
+ from ....Error import Error
13
+ from ....Request import Request
14
+ from ....IO.RequestReader import RequestReader
15
+ from ....Response import Response
16
+ from ....IO.ResponseWriter import ResponseWriter
17
+ from ....Startup import startup
18
+ from ...Server import Server as ServerAdapter
19
+ from ...RequestHandler import handle_request
20
+
21
+ __all__ = (
22
+ 'Server',
23
+ 'ServerRequestHandler',
24
+ )
25
+
26
+
27
+ class Server(
28
+ ThreadingHTTPServer,
29
+ ServerAdapter,
30
+ ):
31
+ """Run the built-in HTTP transport without external dependencies."""
32
+
33
+ daemon_threads = True
34
+ app: Application
35
+ logger = getLogger('DefEssential.Server')
36
+
37
+ def __init__(
38
+ self,
39
+ host: str,
40
+ port: int,
41
+ application: Application,
42
+ *,
43
+ poll_interval: float = 0.5,
44
+ ):
45
+ ServerAdapter.__init__(
46
+ self,
47
+ host,
48
+ port,
49
+ application,
50
+ )
51
+ self.app = application
52
+ self.poll_interval = poll_interval
53
+ try:
54
+ ThreadingHTTPServer.__init__(
55
+ self,
56
+ (
57
+ host,
58
+ port,
59
+ ),
60
+ ServerRequestHandler,
61
+ )
62
+ except Exception:
63
+ self.logger.exception('Server bind failed: adapter=Python host=%s port=%s', host, port)
64
+ raise
65
+ return
66
+
67
+ def run(self) -> None:
68
+ """Close the listening socket and ensure extensions shut down when serving stops."""
69
+ with self:
70
+ self.serve_forever(self.poll_interval)
71
+ self.logger.info('Server stopped: adapter=Python')
72
+ return
73
+
74
+ def serve_forever(
75
+ self,
76
+ poll_interval: float = 0.5,
77
+ ) -> None:
78
+ host = self.server_address[0]
79
+ if not isinstance(host, str):
80
+ raise TypeError('HTTP listen address must be a string')
81
+ self.logger.info('Server starting: adapter=Python host=%s port=%s', host, self.server_port)
82
+ startup(
83
+ self.application,
84
+ 'Python',
85
+ host,
86
+ self.server_port,
87
+ )
88
+ self.app.start()
89
+ self.logger.info('Server ready: adapter=Python host=%s port=%s', host, self.server_port)
90
+ try:
91
+ super().serve_forever(poll_interval=poll_interval)
92
+ except KeyboardInterrupt:
93
+ self.logger.info('Shutdown requested by keyboard interrupt: adapter=Python')
94
+ except Exception:
95
+ self.logger.exception('Server execution failed: adapter=Python')
96
+ raise
97
+ finally:
98
+ self.logger.info('Server stopping: adapter=Python')
99
+ self.app.stop()
100
+ self.logger.info('Server serving stopped: adapter=Python')
101
+ return
102
+
103
+
104
+ class ServerRequestHandler(BaseHTTPRequestHandler):
105
+ server: Server
106
+ protocol_version = 'HTTP/1.1'
107
+ server_version = 'DefEssential'
108
+ logger = getLogger('DefEssential.Server')
109
+
110
+ def request_reader(self) -> RequestReader:
111
+ lengths = self.headers.get_all('Content-Length', [])
112
+ transfers = self.headers.get_all('Transfer-Encoding', [])
113
+ if len(self.headers.get_all('Content-Type', [])) > 1:
114
+ raise Error(
115
+ 400,
116
+ 'Duplicate Content-Type',
117
+ )
118
+ if len(lengths) > 1 or len(transfers) > 1 or (lengths and transfers):
119
+ raise Error(
120
+ 400,
121
+ 'Ambiguous request body framing',
122
+ )
123
+ size: Optional[int] = None
124
+ if lengths:
125
+ if not lengths[0].isascii() or not lengths[0].isdigit():
126
+ raise Error(
127
+ 400,
128
+ 'Invalid Content-Length',
129
+ )
130
+ size = int(lengths[0])
131
+ if transfers and transfers[0].lower() != 'chunked':
132
+ raise Error(
133
+ 400,
134
+ 'Unsupported Transfer-Encoding',
135
+ )
136
+ return RequestReader(
137
+ self.rfile,
138
+ size=size,
139
+ chunked=bool(transfers),
140
+ )
141
+
142
+ def handle_one_request(self) -> None:
143
+ self.close_connection = True
144
+ try:
145
+ self.raw_requestline = self.rfile.readline(65537)
146
+ if len(self.raw_requestline) > 65536:
147
+ self.requestline = ''
148
+ self.request_version = ''
149
+ self.command = ''
150
+ self.send_error(414)
151
+ return
152
+ if not self.raw_requestline:
153
+ return
154
+ if not self.parse_request():
155
+ return
156
+ except ConnectionError:
157
+ self.logger.warning('Client disconnected before request parsing: adapter=Python')
158
+ return
159
+ self.close_connection = True
160
+ writer = ResponseWriter(
161
+ self.begin_response,
162
+ self.wfile,
163
+ method=self.command,
164
+ version=self.request_version,
165
+ )
166
+
167
+ def create_request() -> Request:
168
+ return Request(
169
+ address=self.client_address[0],
170
+ port=self.client_address[1],
171
+ method=self.command,
172
+ uri=self.path,
173
+ headers=dict(self.headers.items()),
174
+ reader=self.request_reader(),
175
+ writer=writer,
176
+ )
177
+
178
+ try:
179
+ handle_request(
180
+ self.server.app,
181
+ create_request,
182
+ writer,
183
+ )
184
+ except Exception:
185
+ # The shared handler logs transport errors; finish by closing the connection.
186
+ self.close_connection = True
187
+ return
188
+
189
+ def begin_response(
190
+ self,
191
+ response: Response,
192
+ chunked: bool,
193
+ ) -> None:
194
+ length = response.header('Content-Length')
195
+ self.response_status = response.status
196
+ self.send_response(
197
+ response.status,
198
+ response.message,
199
+ )
200
+ no_body = (
201
+ self.command == 'HEAD'
202
+ or response.status in (
203
+ 204,
204
+ 205,
205
+ 304,
206
+ )
207
+ or response.status < 200
208
+ )
209
+ upgrade = response.status == 101
210
+ excluded = (
211
+ 'content-length',
212
+ 'transfer-encoding',
213
+ ) if upgrade else (
214
+ 'connection',
215
+ 'content-length',
216
+ 'transfer-encoding',
217
+ )
218
+ for key, value in response.headers():
219
+ if key.lower() not in excluded:
220
+ self.send_header(
221
+ key,
222
+ value,
223
+ )
224
+ if chunked and not no_body:
225
+ self.send_header(
226
+ 'Transfer-Encoding',
227
+ 'chunked',
228
+ )
229
+ elif length is not None and response.status not in (
230
+ 204,
231
+ 205,
232
+ 304,
233
+ ):
234
+ self.send_header(
235
+ 'Content-Length',
236
+ length,
237
+ )
238
+ if response.status == 205:
239
+ self.send_header(
240
+ 'Content-Length',
241
+ '0',
242
+ )
243
+ if not upgrade:
244
+ self.send_header(
245
+ 'Connection',
246
+ 'close',
247
+ )
248
+ self.end_headers()
249
+ return
250
+
251
+ def log_message(
252
+ self,
253
+ format: str,
254
+ *args: object,
255
+ ) -> None:
256
+ self.logger.debug(
257
+ 'HTTP parser diagnostic: method=%s',
258
+ ascii(self.command[:32]) if self.command is not None else None,
259
+ )
260
+ return
261
+
262
+ def log_request(
263
+ self,
264
+ code: Union[int, str] = '-',
265
+ size: Union[int, str] = '-',
266
+ ) -> None:
267
+ """Keep shared result logging separate because selecting headers does not complete transmission."""
268
+ self.logger.debug(
269
+ 'HTTP response headers selected: status=%s method=%s',
270
+ code,
271
+ ascii(self.command[:32]) if self.command is not None else None,
272
+ )
273
+ return
@@ -0,0 +1,5 @@
1
+ from .Server import Server
2
+
3
+ __all__ = (
4
+ 'Server',
5
+ )