hypern 0.3.0__cp312-cp312-win_amd64.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.
- hypern/__init__.py +4 -0
- hypern/application.py +405 -0
- hypern/args_parser.py +59 -0
- hypern/auth/__init__.py +0 -0
- hypern/auth/authorization.py +2 -0
- hypern/background.py +4 -0
- hypern/caching/__init__.py +0 -0
- hypern/caching/base/__init__.py +8 -0
- hypern/caching/base/backend.py +3 -0
- hypern/caching/base/key_maker.py +8 -0
- hypern/caching/cache_manager.py +56 -0
- hypern/caching/cache_tag.py +10 -0
- hypern/caching/custom_key_maker.py +11 -0
- hypern/caching/redis_backend.py +3 -0
- hypern/cli/__init__.py +0 -0
- hypern/cli/commands.py +0 -0
- hypern/config.py +149 -0
- hypern/datastructures.py +40 -0
- hypern/db/__init__.py +0 -0
- hypern/db/nosql/__init__.py +25 -0
- hypern/db/nosql/addons/__init__.py +4 -0
- hypern/db/nosql/addons/color.py +16 -0
- hypern/db/nosql/addons/daterange.py +30 -0
- hypern/db/nosql/addons/encrypted.py +53 -0
- hypern/db/nosql/addons/password.py +134 -0
- hypern/db/nosql/addons/unicode.py +10 -0
- hypern/db/sql/__init__.py +179 -0
- hypern/db/sql/addons/__init__.py +14 -0
- hypern/db/sql/addons/color.py +16 -0
- hypern/db/sql/addons/daterange.py +23 -0
- hypern/db/sql/addons/datetime.py +22 -0
- hypern/db/sql/addons/encrypted.py +58 -0
- hypern/db/sql/addons/password.py +171 -0
- hypern/db/sql/addons/ts_vector.py +46 -0
- hypern/db/sql/addons/unicode.py +15 -0
- hypern/db/sql/repository.py +290 -0
- hypern/enum.py +13 -0
- hypern/exceptions.py +97 -0
- hypern/hypern.cp312-win_amd64.pyd +0 -0
- hypern/hypern.pyi +295 -0
- hypern/i18n/__init__.py +0 -0
- hypern/logging/__init__.py +3 -0
- hypern/logging/logger.py +82 -0
- hypern/middleware/__init__.py +5 -0
- hypern/middleware/base.py +18 -0
- hypern/middleware/cors.py +38 -0
- hypern/middleware/i18n.py +1 -0
- hypern/middleware/limit.py +176 -0
- hypern/openapi/__init__.py +5 -0
- hypern/openapi/schemas.py +53 -0
- hypern/openapi/swagger.py +3 -0
- hypern/processpool.py +137 -0
- hypern/py.typed +0 -0
- hypern/reload.py +60 -0
- hypern/response/__init__.py +3 -0
- hypern/response/response.py +134 -0
- hypern/routing/__init__.py +4 -0
- hypern/routing/dispatcher.py +67 -0
- hypern/routing/endpoint.py +30 -0
- hypern/routing/parser.py +100 -0
- hypern/routing/route.py +284 -0
- hypern/scheduler.py +5 -0
- hypern/security.py +44 -0
- hypern/worker.py +30 -0
- hypern/ws.py +16 -0
- hypern-0.3.0.dist-info/METADATA +128 -0
- hypern-0.3.0.dist-info/RECORD +69 -0
- hypern-0.3.0.dist-info/WHEEL +4 -0
- hypern-0.3.0.dist-info/licenses/LICENSE +24 -0
hypern/exceptions.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Any
|
|
3
|
+
from hypern.enum import ErrorCode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseException(Exception):
|
|
7
|
+
def __init__(self, msg: str = "", *args: Any) -> None:
|
|
8
|
+
super().__init__(*args)
|
|
9
|
+
self.msg = msg
|
|
10
|
+
self.status = 400
|
|
11
|
+
self.error_code = ErrorCode.UNKNOWN_ERROR
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BadRequest(BaseException):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
msg: str = "Bad request",
|
|
18
|
+
error_code: str = ErrorCode.BAD_REQUEST,
|
|
19
|
+
*args: Any,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(msg, *args)
|
|
22
|
+
self.error_code = error_code
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidationError(BaseException):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
msg: str = "Validation error",
|
|
29
|
+
error_code: str = ErrorCode.VALIDATION_ERROR,
|
|
30
|
+
*args: Any,
|
|
31
|
+
) -> None:
|
|
32
|
+
super().__init__(msg, *args)
|
|
33
|
+
self.error_code = error_code
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Forbidden(BaseException):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
msg: str = "Forbidden",
|
|
40
|
+
error_code: str = ErrorCode.FORBIDDEN,
|
|
41
|
+
*args: Any,
|
|
42
|
+
) -> None:
|
|
43
|
+
super().__init__(msg, *args)
|
|
44
|
+
self.status = 403
|
|
45
|
+
self.error_code = error_code
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NotFound(BaseException):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
msg: str = "NotFound",
|
|
52
|
+
error_code: str = ErrorCode.NOT_FOUND,
|
|
53
|
+
*args: Any,
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__(msg, *args)
|
|
56
|
+
self.status = 404
|
|
57
|
+
self.error_code = error_code
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MethodNotAllow(BaseException):
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
msg: str = "Method not allow",
|
|
64
|
+
error_code: str = ErrorCode.METHOD_NOT_ALLOW,
|
|
65
|
+
*args: Any,
|
|
66
|
+
) -> None:
|
|
67
|
+
super().__init__(msg, *args)
|
|
68
|
+
self.status = 405
|
|
69
|
+
self.error_code = error_code
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class InternalServer(BaseException):
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
msg: str = "Internal server error",
|
|
76
|
+
error_code: str = ErrorCode.SERVER_ERROR,
|
|
77
|
+
*args: Any,
|
|
78
|
+
) -> None:
|
|
79
|
+
super().__init__(msg, *args)
|
|
80
|
+
self.status = 500
|
|
81
|
+
self.error_code = error_code
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Unauthorized(BaseException):
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
msg: str = "Unauthorized",
|
|
88
|
+
error_code: str = ErrorCode.UNAUTHORIZED,
|
|
89
|
+
*args: Any,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(msg, *args)
|
|
92
|
+
self.status = 401
|
|
93
|
+
self.error_code = error_code
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class InvalidPortNumber(Exception):
|
|
97
|
+
pass
|
|
Binary file
|
hypern/hypern.pyi
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Callable, Dict, List, Tuple
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class BaseBackend:
|
|
8
|
+
get: Callable[[str], Any]
|
|
9
|
+
set: Callable[[Any, str, int], None]
|
|
10
|
+
delete_startswith: Callable[[str], None]
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class RedisBackend(BaseBackend):
|
|
14
|
+
url: str
|
|
15
|
+
|
|
16
|
+
get: Callable[[str], Any]
|
|
17
|
+
set: Callable[[Any, str, int], None]
|
|
18
|
+
delete_startswith: Callable[[str], None]
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class BaseSchemaGenerator:
|
|
22
|
+
remove_converter: Callable[[str], str]
|
|
23
|
+
parse_docstring: Callable[[Callable[..., Any]], str]
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SwaggerUI:
|
|
27
|
+
title: str
|
|
28
|
+
openapi_url: str
|
|
29
|
+
|
|
30
|
+
def get_html_content(self) -> str: ...
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class BackgroundTask:
|
|
34
|
+
"""
|
|
35
|
+
A task to be executed in the background
|
|
36
|
+
id: str: The task ID
|
|
37
|
+
function: Callable[..., Any]: The function to be executed
|
|
38
|
+
args: List | Tuple: The arguments to be passed to the function
|
|
39
|
+
kwargs: Dict[str, Any]: The keyword arguments to be passed to the function
|
|
40
|
+
timeout_secs: int: The maximum time in seconds the task is allowed to run
|
|
41
|
+
cancelled: bool: Whether the task is cancelled
|
|
42
|
+
|
|
43
|
+
**Note**: function is currently running with sync mode, so it should be a sync function
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
id: str
|
|
47
|
+
function: Callable[..., Any]
|
|
48
|
+
args: List | Tuple
|
|
49
|
+
kwargs: Dict[str, Any]
|
|
50
|
+
timeout_secs: int
|
|
51
|
+
cancelled: bool
|
|
52
|
+
|
|
53
|
+
def get_id(self) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Get the task ID
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def cancel(self) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Cancel the task
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def is_cancelled(self) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
Check if the task is cancelled
|
|
68
|
+
"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def execute(self) -> Any:
|
|
72
|
+
"""
|
|
73
|
+
Execute the task
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class BackgroundTasks:
|
|
79
|
+
"""
|
|
80
|
+
A collection of tasks to be executed in the background
|
|
81
|
+
|
|
82
|
+
**Note**: Only set tasks. pool, sender, receiver are set by the framework
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def add_task(self, task: BackgroundTask) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Add a task to the collection
|
|
88
|
+
"""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def cancel_task(self, task_id: str) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Cancel a task in the collection
|
|
94
|
+
"""
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def execute_all(self) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Execute all tasks in the collection
|
|
100
|
+
"""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
def execute_task(self, task_id: str) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Execute a task in the collection
|
|
106
|
+
"""
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
class Scheduler:
|
|
110
|
+
def add_job(
|
|
111
|
+
self,
|
|
112
|
+
job_type: str,
|
|
113
|
+
schedule_param: str,
|
|
114
|
+
task: Callable[..., Any],
|
|
115
|
+
timezone: str,
|
|
116
|
+
dependencies: List[str],
|
|
117
|
+
retry_policy: Tuple[int, int, bool] | None = None,
|
|
118
|
+
) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Add a job to the scheduler
|
|
121
|
+
params:
|
|
122
|
+
job_type: str: The type of the job (e.g. "cron", "interval")
|
|
123
|
+
|
|
124
|
+
schedule_param: str: The schedule parameter of the job. interval in seconds for interval jobs, cron expression for cron jobs
|
|
125
|
+
|
|
126
|
+
Exmaple:
|
|
127
|
+
// sec min hour day of month month day of week year
|
|
128
|
+
expression = "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2";
|
|
129
|
+
|
|
130
|
+
task: Callable[..., Any]: The task to be executed
|
|
131
|
+
|
|
132
|
+
timezone: str: The timezone of the job
|
|
133
|
+
|
|
134
|
+
dependencies: List[str]: The IDs of the jobs this job depends on
|
|
135
|
+
|
|
136
|
+
retry_policy: Tuple[int, int, bool] | None: The retry policy of the job. (max_retries, retry_delay_secs, exponential_backoff)
|
|
137
|
+
|
|
138
|
+
return:
|
|
139
|
+
str: The ID of the job
|
|
140
|
+
"""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
def remove_job(self, job_id: str) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Remove a job from the scheduler
|
|
146
|
+
"""
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
def start(self) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Start the scheduler
|
|
152
|
+
"""
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
def stop(self) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Stop the scheduler
|
|
158
|
+
"""
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
def get_job_status(self, job_id: str) -> Tuple[float, float, List[str], int]:
|
|
162
|
+
"""
|
|
163
|
+
Get the status of a job
|
|
164
|
+
"""
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
def get_next_run(self, job_id: str) -> float:
|
|
168
|
+
"""
|
|
169
|
+
Get the next run time of a job
|
|
170
|
+
"""
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
@dataclass
|
|
174
|
+
class FunctionInfo:
|
|
175
|
+
"""
|
|
176
|
+
The function info object passed to the route handler.
|
|
177
|
+
|
|
178
|
+
Attributes:
|
|
179
|
+
handler (Callable): The function to be called
|
|
180
|
+
is_async (bool): Whether the function is async or not
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
handler: Callable
|
|
184
|
+
is_async: bool
|
|
185
|
+
|
|
186
|
+
@dataclass
|
|
187
|
+
class Server:
|
|
188
|
+
router: Router
|
|
189
|
+
websocket_router: Any
|
|
190
|
+
startup_handler: Any
|
|
191
|
+
shutdown_handler: Any
|
|
192
|
+
|
|
193
|
+
def add_route(self, route: Route) -> None: ...
|
|
194
|
+
def set_router(self, router: Router) -> None: ...
|
|
195
|
+
def set_websocket_router(self, websocket_router: WebsocketRouter) -> None: ...
|
|
196
|
+
def start(self, socket: SocketHeld, worker: int, max_blocking_threads: int) -> None: ...
|
|
197
|
+
def inject(self, key: str, value: Any) -> None: ...
|
|
198
|
+
def set_injected(self, injected: Dict[str, Any]) -> None: ...
|
|
199
|
+
def set_before_hooks(self, hooks: List[FunctionInfo]) -> None: ...
|
|
200
|
+
def set_after_hooks(self, hooks: List[FunctionInfo]) -> None: ...
|
|
201
|
+
def set_response_headers(self, headers: Dict[str, str]) -> None: ...
|
|
202
|
+
|
|
203
|
+
class Route:
|
|
204
|
+
path: str
|
|
205
|
+
function: FunctionInfo
|
|
206
|
+
method: str
|
|
207
|
+
|
|
208
|
+
def matches(self, path: str, method: str) -> str: ...
|
|
209
|
+
def clone_route(self) -> Route: ...
|
|
210
|
+
def update_path(self, new_path: str) -> None: ...
|
|
211
|
+
def update_method(self, new_method: str) -> None: ...
|
|
212
|
+
def is_valid(self) -> bool: ...
|
|
213
|
+
def get_path_parans(self) -> List[str]: ...
|
|
214
|
+
def has_parameters(self) -> bool: ...
|
|
215
|
+
def normalized_path(self) -> str: ...
|
|
216
|
+
def same_handler(self, other: Route) -> bool: ...
|
|
217
|
+
|
|
218
|
+
class Router:
|
|
219
|
+
routes: List[Route]
|
|
220
|
+
|
|
221
|
+
def add_route(self, route: Route) -> None: ...
|
|
222
|
+
def remove_route(self, path: str, method: str) -> bool: ...
|
|
223
|
+
def get_route(self, path: str, method) -> Route | None: ...
|
|
224
|
+
def get_routes_by_path(self, path: str) -> List[Route]: ...
|
|
225
|
+
def get_routes_by_method(self, method: str) -> List[Route]: ...
|
|
226
|
+
def extend_route(self, routes: List[Route]) -> None: ...
|
|
227
|
+
|
|
228
|
+
@dataclass
|
|
229
|
+
class SocketHeld:
|
|
230
|
+
socket: Any
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class WebSocketSession:
|
|
234
|
+
sender: Callable[[str], None]
|
|
235
|
+
receiver: Callable[[], str]
|
|
236
|
+
is_closed: bool
|
|
237
|
+
|
|
238
|
+
def send(self, message: str) -> None: ...
|
|
239
|
+
|
|
240
|
+
@dataclass
|
|
241
|
+
class WebsocketRoute:
|
|
242
|
+
path: str
|
|
243
|
+
handler: Callable[[WebSocketSession], None]
|
|
244
|
+
|
|
245
|
+
@dataclass
|
|
246
|
+
class WebsocketRouter:
|
|
247
|
+
path: str
|
|
248
|
+
routes: List[WebsocketRoute]
|
|
249
|
+
|
|
250
|
+
def add_route(self, route: WebsocketRoute) -> None: ...
|
|
251
|
+
def remove_route(self, path: str) -> None: ...
|
|
252
|
+
def extend_route(self, route: WebsocketRoute) -> None: ...
|
|
253
|
+
def clear_routes(self) -> None: ...
|
|
254
|
+
def route_count(self) -> int: ...
|
|
255
|
+
|
|
256
|
+
@dataclass
|
|
257
|
+
class Header:
|
|
258
|
+
headers: Dict[str, str]
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class Response:
|
|
262
|
+
status_code: int
|
|
263
|
+
response_type: str
|
|
264
|
+
headers: Any
|
|
265
|
+
description: str
|
|
266
|
+
file_path: str
|
|
267
|
+
|
|
268
|
+
@dataclass
|
|
269
|
+
class QueryParams:
|
|
270
|
+
queries: Dict[str, List[str]]
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class UploadedFile:
|
|
274
|
+
name: str
|
|
275
|
+
content_type: str
|
|
276
|
+
path: str
|
|
277
|
+
size: int
|
|
278
|
+
content: bytes
|
|
279
|
+
filename: str
|
|
280
|
+
|
|
281
|
+
@dataclass
|
|
282
|
+
class BodyData:
|
|
283
|
+
json: bytes
|
|
284
|
+
files: List[UploadedFile]
|
|
285
|
+
|
|
286
|
+
@dataclass
|
|
287
|
+
class Request:
|
|
288
|
+
query_params: QueryParams
|
|
289
|
+
headers: Dict[str, str]
|
|
290
|
+
path_params: Dict[str, str]
|
|
291
|
+
body: BodyData
|
|
292
|
+
method: str
|
|
293
|
+
remote_addr: str
|
|
294
|
+
timestamp: float
|
|
295
|
+
context_id: str
|
hypern/i18n/__init__.py
ADDED
|
File without changes
|
hypern/logging/logger.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from copy import copy
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Literal, Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
TRACE_LOG_LEVEL = 5
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ColourizedFormatter(logging.Formatter):
|
|
14
|
+
level_name_colors = {
|
|
15
|
+
TRACE_LOG_LEVEL: lambda level_name: click.style(str(level_name), fg="blue"),
|
|
16
|
+
logging.DEBUG: lambda level_name: click.style(str(level_name), fg="cyan"),
|
|
17
|
+
logging.INFO: lambda level_name: click.style(str(level_name), fg="green"),
|
|
18
|
+
logging.WARNING: lambda level_name: click.style(str(level_name), fg="yellow"),
|
|
19
|
+
logging.ERROR: lambda level_name: click.style(str(level_name), fg="red"),
|
|
20
|
+
logging.CRITICAL: lambda level_name: click.style(str(level_name), fg="bright_red"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
fmt: Optional[str] = None,
|
|
26
|
+
datefmt: Optional[str] = None,
|
|
27
|
+
style: Literal["%", "{", "$"] = "%",
|
|
28
|
+
use_colors: Optional[bool] = None,
|
|
29
|
+
):
|
|
30
|
+
if use_colors in (True, False):
|
|
31
|
+
self.use_colors = use_colors
|
|
32
|
+
else:
|
|
33
|
+
self.use_colors = sys.stdout.isatty()
|
|
34
|
+
super().__init__(fmt=fmt, datefmt=datefmt, style=style)
|
|
35
|
+
|
|
36
|
+
def color_level_name(self, level_name: str, level_no: int) -> str:
|
|
37
|
+
def default(level_name: str) -> str:
|
|
38
|
+
return str(level_name)
|
|
39
|
+
|
|
40
|
+
func = self.level_name_colors.get(level_no, default)
|
|
41
|
+
return func(level_name)
|
|
42
|
+
|
|
43
|
+
def should_use_colors(self) -> bool:
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
47
|
+
recordcopy = copy(record)
|
|
48
|
+
levelname = recordcopy.levelname
|
|
49
|
+
process = recordcopy.process
|
|
50
|
+
created = recordcopy.created
|
|
51
|
+
filename = recordcopy.filename
|
|
52
|
+
module = recordcopy.module
|
|
53
|
+
lineno = recordcopy.lineno
|
|
54
|
+
separator = " " * (5 - len(recordcopy.levelname))
|
|
55
|
+
if self.use_colors:
|
|
56
|
+
levelname = self.color_level_name(levelname, recordcopy.levelno)
|
|
57
|
+
if "color_message" in recordcopy.__dict__:
|
|
58
|
+
recordcopy.msg = recordcopy.__dict__["color_message"]
|
|
59
|
+
recordcopy.__dict__["message"] = recordcopy.getMessage()
|
|
60
|
+
recordcopy.__dict__["levelprefix"] = levelname + separator
|
|
61
|
+
recordcopy.__dict__["process"] = click.style(str(process), fg="blue")
|
|
62
|
+
recordcopy.__dict__["asctime"] = click.style(datetime.fromtimestamp(created, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ"), fg=(101, 111, 104))
|
|
63
|
+
recordcopy.__dict__["filename"] = click.style(f"{module}/{filename}:{lineno}:", fg=(101, 111, 104))
|
|
64
|
+
return super().formatMessage(recordcopy)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DefaultFormatter(ColourizedFormatter):
|
|
68
|
+
def should_use_colors(self) -> bool:
|
|
69
|
+
return sys.stderr.isatty()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def create_logger(name) -> logging.Logger:
|
|
73
|
+
logger = logging.getLogger(name)
|
|
74
|
+
logger.setLevel(logging.DEBUG)
|
|
75
|
+
formatter = DefaultFormatter(fmt="%(asctime)s %(levelprefix)s %(filename)s %(message)s", use_colors=True, datefmt="%Y-%m-%d %H:%M:%S")
|
|
76
|
+
handler = logging.StreamHandler()
|
|
77
|
+
handler.setFormatter(formatter)
|
|
78
|
+
logger.addHandler(handler)
|
|
79
|
+
return logger
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
logger = create_logger("hypern")
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .base import Middleware
|
|
2
|
+
from .cors import CORSMiddleware
|
|
3
|
+
from .limit import RateLimitMiddleware, StorageBackend, RedisBackend, InMemoryBackend
|
|
4
|
+
|
|
5
|
+
__all__ = ["Middleware", "CORSMiddleware", "RateLimitMiddleware", "StorageBackend", "RedisBackend", "InMemoryBackend"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from hypern.hypern import Response, Request
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# The `Middleware` class is an abstract base class with abstract methods `before_request` and
|
|
6
|
+
# `after_request` for handling requests and responses in a web application.
|
|
7
|
+
class Middleware(ABC):
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.app = None
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def before_request(self, request: Request):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def after_request(self, response: Response):
|
|
18
|
+
pass
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .base import Middleware
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CORSMiddleware(Middleware):
|
|
6
|
+
"""
|
|
7
|
+
The `CORSMiddleware` class is used to add CORS headers to the response based on specified origins,
|
|
8
|
+
methods, and headers.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, allow_origins: List[str] = None, allow_methods: List[str] = None, allow_headers: List[str] = None) -> None:
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.allow_origins = allow_origins or []
|
|
14
|
+
self.allow_methods = allow_methods or []
|
|
15
|
+
self.allow_headers = allow_headers or []
|
|
16
|
+
|
|
17
|
+
def before_request(self, request):
|
|
18
|
+
return request
|
|
19
|
+
|
|
20
|
+
def after_request(self, response):
|
|
21
|
+
"""
|
|
22
|
+
The `after_request` function adds Access-Control headers to the response based on specified origins,
|
|
23
|
+
methods, and headers.
|
|
24
|
+
|
|
25
|
+
:param response: The `after_request` method is used to add CORS (Cross-Origin Resource Sharing)
|
|
26
|
+
headers to the response object before sending it back to the client. The parameters used in this
|
|
27
|
+
method are:
|
|
28
|
+
:return: The `response` object is being returned from the `after_request` method.
|
|
29
|
+
"""
|
|
30
|
+
for origin in self.allow_origins:
|
|
31
|
+
self.app.add_response_header("Access-Control-Allow-Origin", origin)
|
|
32
|
+
self.app.add_response_header(
|
|
33
|
+
"Access-Control-Allow-Methods",
|
|
34
|
+
", ".join([method.upper() for method in self.allow_methods]),
|
|
35
|
+
)
|
|
36
|
+
self.app.add_response_header("Access-Control-Allow-Headers", ", ".join(self.allow_headers))
|
|
37
|
+
self.app.add_response_header("Access-Control-Allow-Credentials", "true")
|
|
38
|
+
return response
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# comming soon
|