DefEssential 0.1.8__25-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 +152 -0
- DefEssential/Adapter/Implements/Gunicorn/Transport.py +171 -0
- DefEssential/Adapter/Implements/Gunicorn/__init__.py +5 -0
- DefEssential/Adapter/Implements/Python/Server.py +273 -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 +137 -0
- DefEssential/Adapter/Implements/Uvicorn/WebSocket.py +222 -0
- DefEssential/Adapter/Implements/Uvicorn/__init__.py +5 -0
- DefEssential/Adapter/Implements/__init__.py +3 -0
- DefEssential/Adapter/RequestHandler.py +109 -0
- DefEssential/Adapter/Server.py +54 -0
- DefEssential/Adapter/WSGI.py +219 -0
- DefEssential/Adapter/__init__.py +5 -0
- DefEssential/Application.py +657 -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 +191 -0
- DefEssential/Extensions/File.py +95 -0
- DefEssential/Extensions/FileSystem.py +195 -0
- DefEssential/Extensions/Metrics.py +511 -0
- DefEssential/Extensions/Ping.py +93 -0
- DefEssential/Extensions/Specifications.py +342 -0
- DefEssential/Extensions/Status.html +207 -0
- DefEssential/Extensions/Status.py +110 -0
- DefEssential/Extensions/Swagger.html +77 -0
- DefEssential/Extensions/Swagger.py +117 -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 +242 -0
- DefEssential/IO/WebSocket/WebSocket.py +288 -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 +89 -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 +116 -0
- DefEssential/Runners/RunFactory.py +26 -0
- DefEssential/Runners/RunOptions.py +77 -0
- DefEssential/Runners/RunRequest.py +198 -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.8.dist-info/METADATA +86 -0
- defessential-0.1.8.dist-info/RECORD +104 -0
- defessential-0.1.8.dist-info/WHEEL +6 -0
- defessential-0.1.8.dist-info/licenses/LICENSE +674 -0
- defessential-0.1.8.dist-info/top_level.txt +1 -0
DefEssential/Content.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import (
|
|
3
|
+
Any,
|
|
4
|
+
Optional,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
'Content',
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Content:
|
|
14
|
+
"""Describe input or output content formats, schemas, and examples."""
|
|
15
|
+
|
|
16
|
+
format: str
|
|
17
|
+
schema: Optional[object] = None
|
|
18
|
+
example: Any = None
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from http.cookies import (
|
|
3
|
+
CookieError,
|
|
4
|
+
SimpleCookie,
|
|
5
|
+
)
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Optional,
|
|
9
|
+
Sequence,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .Error import ClientError
|
|
13
|
+
from .Request import Request
|
|
14
|
+
from .Validators import Credentials
|
|
15
|
+
|
|
16
|
+
__all__ = (
|
|
17
|
+
'HTTPAuthorization',
|
|
18
|
+
'Query',
|
|
19
|
+
'Header',
|
|
20
|
+
'Cookie',
|
|
21
|
+
'HTTP',
|
|
22
|
+
'OAuth2Implicit',
|
|
23
|
+
'OAuth2Password',
|
|
24
|
+
'OAuth2ClientCredentials',
|
|
25
|
+
'OAuth2AuthorizationCode',
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class HTTPAuthorization:
|
|
31
|
+
"""Represent the scheme and credentials of an HTTP authentication header."""
|
|
32
|
+
|
|
33
|
+
scheme: str
|
|
34
|
+
credentials: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Query(Credentials):
|
|
38
|
+
"""Extract a single credential value from the query string."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
name: str,
|
|
43
|
+
):
|
|
44
|
+
self.key = name
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
def __call__(
|
|
48
|
+
self,
|
|
49
|
+
request: Request,
|
|
50
|
+
) -> Optional[str]:
|
|
51
|
+
value = request.qs.get(self.key)
|
|
52
|
+
if value is not None and not isinstance(value, str):
|
|
53
|
+
raise ClientError(
|
|
54
|
+
'Credential query parameter must have a single value',
|
|
55
|
+
status=401,
|
|
56
|
+
)
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def name(self) -> str:
|
|
61
|
+
return 'Query'
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def format(self) -> dict:
|
|
65
|
+
return {
|
|
66
|
+
'type': 'apiKey',
|
|
67
|
+
'in': 'query',
|
|
68
|
+
'name': self.key,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Header(Query):
|
|
73
|
+
"""Extract credentials from the specified header."""
|
|
74
|
+
|
|
75
|
+
def __call__(
|
|
76
|
+
self,
|
|
77
|
+
request: Request,
|
|
78
|
+
) -> Optional[str]:
|
|
79
|
+
return request.header(self.key)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def name(self) -> str:
|
|
83
|
+
return 'Header'
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def format(self) -> dict:
|
|
87
|
+
return {
|
|
88
|
+
'type': 'apiKey',
|
|
89
|
+
'in': 'header',
|
|
90
|
+
'name': self.key,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def headers(self) -> Sequence[str]:
|
|
95
|
+
return (self.key,)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Cookie(Query):
|
|
99
|
+
"""Extract the value of the specified cookie."""
|
|
100
|
+
|
|
101
|
+
def __call__(
|
|
102
|
+
self,
|
|
103
|
+
request: Request,
|
|
104
|
+
) -> Optional[str]:
|
|
105
|
+
value = request.header('Cookie')
|
|
106
|
+
if value is None:
|
|
107
|
+
return None
|
|
108
|
+
cookies: SimpleCookie = SimpleCookie()
|
|
109
|
+
try:
|
|
110
|
+
cookies.load(value)
|
|
111
|
+
except CookieError as error:
|
|
112
|
+
raise ClientError(
|
|
113
|
+
'Invalid authentication cookie',
|
|
114
|
+
status=401,
|
|
115
|
+
) from error
|
|
116
|
+
cookie = cookies.get(self.key)
|
|
117
|
+
return cookie.value if cookie is not None else None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def name(self) -> str:
|
|
121
|
+
return 'Cookie'
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def format(self) -> dict:
|
|
125
|
+
return {
|
|
126
|
+
'type': 'apiKey',
|
|
127
|
+
'in': 'cookie',
|
|
128
|
+
'name': self.key,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class HTTP(Credentials):
|
|
133
|
+
"""Parse the Authorization header and verify the specified scheme."""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
scheme: str,
|
|
138
|
+
format: Optional[str] = None,
|
|
139
|
+
):
|
|
140
|
+
self.scheme = scheme
|
|
141
|
+
self.fmt = format
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
def __call__(
|
|
145
|
+
self,
|
|
146
|
+
request: Request,
|
|
147
|
+
) -> Optional[HTTPAuthorization]:
|
|
148
|
+
value = request.header('Authorization')
|
|
149
|
+
if value is None:
|
|
150
|
+
return None
|
|
151
|
+
scheme, separator, credentials = value.partition(' ')
|
|
152
|
+
if not separator or not credentials.strip() or scheme.lower() != self.scheme.lower():
|
|
153
|
+
raise ClientError(
|
|
154
|
+
'Invalid authentication scheme or credentials',
|
|
155
|
+
status=401,
|
|
156
|
+
)
|
|
157
|
+
return HTTPAuthorization(
|
|
158
|
+
scheme=scheme,
|
|
159
|
+
credentials=credentials.strip(),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def name(self) -> str:
|
|
164
|
+
return 'HTTP'
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def format(self) -> dict:
|
|
168
|
+
value = {
|
|
169
|
+
'type': 'http',
|
|
170
|
+
'scheme': self.scheme.lower(),
|
|
171
|
+
}
|
|
172
|
+
if self.fmt is not None:
|
|
173
|
+
value['bearerFormat'] = self.fmt
|
|
174
|
+
return value
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def headers(self) -> Sequence[str]:
|
|
178
|
+
return ('Authorization',)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class OAuth2Implicit(HTTP):
|
|
182
|
+
"""Configure bearer authentication metadata for the OAuth2 implicit flow."""
|
|
183
|
+
|
|
184
|
+
flow = 'implicit'
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
authorizationUrl: Optional[str] = None,
|
|
189
|
+
):
|
|
190
|
+
super().__init__('Bearer')
|
|
191
|
+
self.options: dict[str, Any] = {'scopes': {}}
|
|
192
|
+
if authorizationUrl is not None:
|
|
193
|
+
self.options['authorizationUrl'] = authorizationUrl
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def name(self) -> str:
|
|
198
|
+
return type(self).__name__
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def format(self) -> dict:
|
|
202
|
+
return {
|
|
203
|
+
'type': 'oauth2',
|
|
204
|
+
'flows': {self.flow: dict(self.options)},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class OAuth2Password(OAuth2Implicit):
|
|
209
|
+
"""Configure token and refresh URLs for the OAuth2 password flow."""
|
|
210
|
+
|
|
211
|
+
flow = 'password'
|
|
212
|
+
|
|
213
|
+
def __init__(
|
|
214
|
+
self,
|
|
215
|
+
tokenUrl: Optional[str] = None,
|
|
216
|
+
refreshUrl: Optional[str] = None,
|
|
217
|
+
):
|
|
218
|
+
super().__init__()
|
|
219
|
+
if tokenUrl is not None:
|
|
220
|
+
self.options['tokenUrl'] = tokenUrl
|
|
221
|
+
if refreshUrl is not None:
|
|
222
|
+
self.options['refreshUrl'] = refreshUrl
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class OAuth2ClientCredentials(OAuth2Password):
|
|
227
|
+
"""Represent the OAuth2 clientCredentials flow."""
|
|
228
|
+
|
|
229
|
+
flow = 'clientCredentials'
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class OAuth2AuthorizationCode(OAuth2Password):
|
|
233
|
+
"""Configure authorization and token URLs for the OAuth2 authorizationCode flow."""
|
|
234
|
+
|
|
235
|
+
flow = 'authorizationCode'
|
|
236
|
+
|
|
237
|
+
def __init__(
|
|
238
|
+
self,
|
|
239
|
+
authorizationUrl: Optional[str] = None,
|
|
240
|
+
tokenUrl: Optional[str] = None,
|
|
241
|
+
):
|
|
242
|
+
super().__init__(tokenUrl=tokenUrl)
|
|
243
|
+
if authorizationUrl is not None:
|
|
244
|
+
self.options['authorizationUrl'] = authorizationUrl
|
|
245
|
+
return
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
from .Error import ClientError
|
|
4
|
+
from .Request import Request
|
|
5
|
+
from .RequestRunner import RequestProperties
|
|
6
|
+
from .Response import Response
|
|
7
|
+
from .Validators import (
|
|
8
|
+
Auth,
|
|
9
|
+
Headers,
|
|
10
|
+
Origin,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
'CrossOriginResourceSharing',
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CrossOriginResourceSharing:
|
|
19
|
+
"""Apply the Cross-Origin Resource Sharing policy configured for a request."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
properties: RequestProperties,
|
|
24
|
+
):
|
|
25
|
+
self.request_properties = properties
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
def origin_policies(self) -> Sequence[Origin]:
|
|
29
|
+
value = self.request_properties.origin
|
|
30
|
+
if isinstance(value, Origin):
|
|
31
|
+
return (value,)
|
|
32
|
+
if isinstance(value, Sequence):
|
|
33
|
+
return tuple(item for item in value if isinstance(item, Origin))
|
|
34
|
+
return ()
|
|
35
|
+
|
|
36
|
+
def has_origin_policy(self) -> bool:
|
|
37
|
+
return bool(self.origin_policies())
|
|
38
|
+
|
|
39
|
+
def validate_origin(
|
|
40
|
+
self,
|
|
41
|
+
request: Request,
|
|
42
|
+
) -> None:
|
|
43
|
+
policies = self.origin_policies()
|
|
44
|
+
if policies:
|
|
45
|
+
if any(policy.allows(request.header('Origin')) for policy in policies):
|
|
46
|
+
return
|
|
47
|
+
error = next((policy.error for policy in policies if policy.error is not None), None)
|
|
48
|
+
raise error if error is not None else ClientError(
|
|
49
|
+
'Origin not allowed',
|
|
50
|
+
status=403,
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
def allowed_request_headers(self) -> list[str]:
|
|
55
|
+
headers = []
|
|
56
|
+
if isinstance(self.request_properties.headers, Headers):
|
|
57
|
+
headers.extend(self.request_properties.headers.headers)
|
|
58
|
+
if isinstance(self.request_properties.auth, Auth):
|
|
59
|
+
headers.extend(self.request_properties.auth.credentials.headers)
|
|
60
|
+
if self.request_properties.body is not None:
|
|
61
|
+
headers.append('Content-Type')
|
|
62
|
+
return list(dict.fromkeys(headers))
|
|
63
|
+
|
|
64
|
+
def apply_to_response(
|
|
65
|
+
self,
|
|
66
|
+
request: Request,
|
|
67
|
+
response: Response,
|
|
68
|
+
*,
|
|
69
|
+
preflight: bool = False,
|
|
70
|
+
) -> Response:
|
|
71
|
+
if not self.has_origin_policy():
|
|
72
|
+
return response
|
|
73
|
+
origin = request.header('Origin')
|
|
74
|
+
if origin is None:
|
|
75
|
+
return response
|
|
76
|
+
self.validate_origin(request)
|
|
77
|
+
response.header(
|
|
78
|
+
'Access-Control-Allow-Origin',
|
|
79
|
+
origin,
|
|
80
|
+
)
|
|
81
|
+
vary = response.header('Vary')
|
|
82
|
+
fields = [item.strip() for item in vary.split(',')] if vary else []
|
|
83
|
+
if 'origin' not in (item.lower() for item in fields) and '*' not in fields:
|
|
84
|
+
fields.append('Origin')
|
|
85
|
+
response.header(
|
|
86
|
+
'Vary',
|
|
87
|
+
', '.join(fields),
|
|
88
|
+
)
|
|
89
|
+
if self.request_properties.auth is not None:
|
|
90
|
+
response.header(
|
|
91
|
+
'Access-Control-Allow-Credentials',
|
|
92
|
+
'true',
|
|
93
|
+
)
|
|
94
|
+
if not preflight:
|
|
95
|
+
expose = [
|
|
96
|
+
key for key, _ in response.headers()
|
|
97
|
+
if key.lower() not in (
|
|
98
|
+
'set-cookie',
|
|
99
|
+
'content-length',
|
|
100
|
+
) and not key.lower().startswith('access-control-')
|
|
101
|
+
]
|
|
102
|
+
if expose:
|
|
103
|
+
response.header(
|
|
104
|
+
'Access-Control-Expose-Headers',
|
|
105
|
+
', '.join(expose),
|
|
106
|
+
)
|
|
107
|
+
return response
|