hypern 0.3.0__cp310-cp310-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.
Files changed (69) hide show
  1. hypern/__init__.py +4 -0
  2. hypern/application.py +405 -0
  3. hypern/args_parser.py +59 -0
  4. hypern/auth/__init__.py +0 -0
  5. hypern/auth/authorization.py +2 -0
  6. hypern/background.py +4 -0
  7. hypern/caching/__init__.py +0 -0
  8. hypern/caching/base/__init__.py +8 -0
  9. hypern/caching/base/backend.py +3 -0
  10. hypern/caching/base/key_maker.py +8 -0
  11. hypern/caching/cache_manager.py +56 -0
  12. hypern/caching/cache_tag.py +10 -0
  13. hypern/caching/custom_key_maker.py +11 -0
  14. hypern/caching/redis_backend.py +3 -0
  15. hypern/cli/__init__.py +0 -0
  16. hypern/cli/commands.py +0 -0
  17. hypern/config.py +149 -0
  18. hypern/datastructures.py +40 -0
  19. hypern/db/__init__.py +0 -0
  20. hypern/db/nosql/__init__.py +25 -0
  21. hypern/db/nosql/addons/__init__.py +4 -0
  22. hypern/db/nosql/addons/color.py +16 -0
  23. hypern/db/nosql/addons/daterange.py +30 -0
  24. hypern/db/nosql/addons/encrypted.py +53 -0
  25. hypern/db/nosql/addons/password.py +134 -0
  26. hypern/db/nosql/addons/unicode.py +10 -0
  27. hypern/db/sql/__init__.py +179 -0
  28. hypern/db/sql/addons/__init__.py +14 -0
  29. hypern/db/sql/addons/color.py +16 -0
  30. hypern/db/sql/addons/daterange.py +23 -0
  31. hypern/db/sql/addons/datetime.py +22 -0
  32. hypern/db/sql/addons/encrypted.py +58 -0
  33. hypern/db/sql/addons/password.py +171 -0
  34. hypern/db/sql/addons/ts_vector.py +46 -0
  35. hypern/db/sql/addons/unicode.py +15 -0
  36. hypern/db/sql/repository.py +290 -0
  37. hypern/enum.py +13 -0
  38. hypern/exceptions.py +97 -0
  39. hypern/hypern.cp310-win_amd64.pyd +0 -0
  40. hypern/hypern.pyi +295 -0
  41. hypern/i18n/__init__.py +0 -0
  42. hypern/logging/__init__.py +3 -0
  43. hypern/logging/logger.py +82 -0
  44. hypern/middleware/__init__.py +5 -0
  45. hypern/middleware/base.py +18 -0
  46. hypern/middleware/cors.py +38 -0
  47. hypern/middleware/i18n.py +1 -0
  48. hypern/middleware/limit.py +176 -0
  49. hypern/openapi/__init__.py +5 -0
  50. hypern/openapi/schemas.py +53 -0
  51. hypern/openapi/swagger.py +3 -0
  52. hypern/processpool.py +137 -0
  53. hypern/py.typed +0 -0
  54. hypern/reload.py +60 -0
  55. hypern/response/__init__.py +3 -0
  56. hypern/response/response.py +134 -0
  57. hypern/routing/__init__.py +4 -0
  58. hypern/routing/dispatcher.py +67 -0
  59. hypern/routing/endpoint.py +30 -0
  60. hypern/routing/parser.py +100 -0
  61. hypern/routing/route.py +284 -0
  62. hypern/scheduler.py +5 -0
  63. hypern/security.py +44 -0
  64. hypern/worker.py +30 -0
  65. hypern/ws.py +16 -0
  66. hypern-0.3.0.dist-info/METADATA +128 -0
  67. hypern-0.3.0.dist-info/RECORD +69 -0
  68. hypern-0.3.0.dist-info/WHEEL +4 -0
  69. 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
File without changes
@@ -0,0 +1,3 @@
1
+ from .logger import logger
2
+
3
+ __all__ = ["logger"]
@@ -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