hypern 0.3.11__cp310-cp310-musllinux_1_2_armv7l.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 +24 -0
- hypern/application.py +495 -0
- hypern/args_parser.py +73 -0
- hypern/auth/__init__.py +0 -0
- hypern/auth/authorization.py +2 -0
- hypern/background.py +4 -0
- hypern/caching/__init__.py +6 -0
- hypern/caching/backend.py +31 -0
- hypern/caching/redis_backend.py +201 -0
- hypern/caching/strategies.py +208 -0
- hypern/cli/__init__.py +0 -0
- hypern/cli/commands.py +0 -0
- hypern/config.py +246 -0
- hypern/database/__init__.py +0 -0
- hypern/database/sqlalchemy/__init__.py +4 -0
- hypern/database/sqlalchemy/config.py +66 -0
- hypern/database/sqlalchemy/repository.py +290 -0
- hypern/database/sqlx/__init__.py +36 -0
- hypern/database/sqlx/field.py +246 -0
- hypern/database/sqlx/migrate.py +263 -0
- hypern/database/sqlx/model.py +117 -0
- hypern/database/sqlx/query.py +904 -0
- hypern/datastructures.py +40 -0
- hypern/enum.py +13 -0
- hypern/exceptions/__init__.py +34 -0
- hypern/exceptions/base.py +62 -0
- hypern/exceptions/common.py +12 -0
- hypern/exceptions/errors.py +15 -0
- hypern/exceptions/formatters.py +56 -0
- hypern/exceptions/http.py +76 -0
- hypern/gateway/__init__.py +6 -0
- hypern/gateway/aggregator.py +32 -0
- hypern/gateway/gateway.py +41 -0
- hypern/gateway/proxy.py +60 -0
- hypern/gateway/service.py +52 -0
- hypern/hypern.cpython-310-arm-linux-gnueabihf.so +0 -0
- hypern/hypern.pyi +333 -0
- hypern/i18n/__init__.py +0 -0
- hypern/logging/__init__.py +3 -0
- hypern/logging/logger.py +82 -0
- hypern/middleware/__init__.py +17 -0
- hypern/middleware/base.py +13 -0
- hypern/middleware/cache.py +177 -0
- hypern/middleware/compress.py +78 -0
- hypern/middleware/cors.py +41 -0
- hypern/middleware/i18n.py +1 -0
- hypern/middleware/limit.py +177 -0
- hypern/middleware/security.py +184 -0
- hypern/openapi/__init__.py +5 -0
- hypern/openapi/schemas.py +51 -0
- hypern/openapi/swagger.py +3 -0
- hypern/processpool.py +139 -0
- hypern/py.typed +0 -0
- hypern/reload.py +46 -0
- hypern/response/__init__.py +3 -0
- hypern/response/response.py +142 -0
- hypern/routing/__init__.py +5 -0
- hypern/routing/dispatcher.py +70 -0
- hypern/routing/endpoint.py +30 -0
- hypern/routing/parser.py +98 -0
- hypern/routing/queue.py +175 -0
- hypern/routing/route.py +280 -0
- hypern/scheduler.py +5 -0
- hypern/worker.py +274 -0
- hypern/ws/__init__.py +4 -0
- hypern/ws/channel.py +80 -0
- hypern/ws/heartbeat.py +74 -0
- hypern/ws/room.py +76 -0
- hypern/ws/route.py +26 -0
- hypern-0.3.11.dist-info/METADATA +134 -0
- hypern-0.3.11.dist-info/RECORD +74 -0
- hypern-0.3.11.dist-info/WHEEL +4 -0
- hypern-0.3.11.dist-info/licenses/LICENSE +24 -0
- hypern.libs/libgcc_s-5b5488a6.so.1 +0 -0
hypern/__init__.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
from hypern.logging import logger
|
2
|
+
from hypern.routing import HTTPEndpoint, QueuedHTTPEndpoint, Route
|
3
|
+
from hypern.ws import WebsocketRoute, WebSocketSession
|
4
|
+
|
5
|
+
from .application import Hypern
|
6
|
+
from .hypern import Request, Response
|
7
|
+
from .response import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse, RedirectResponse
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"Hypern",
|
11
|
+
"Request",
|
12
|
+
"Response",
|
13
|
+
"Route",
|
14
|
+
"HTTPEndpoint",
|
15
|
+
"QueuedHTTPEndpoint",
|
16
|
+
"WebsocketRoute",
|
17
|
+
"WebSocketSession",
|
18
|
+
"FileResponse",
|
19
|
+
"HTMLResponse",
|
20
|
+
"JSONResponse",
|
21
|
+
"PlainTextResponse",
|
22
|
+
"RedirectResponse",
|
23
|
+
"logger",
|
24
|
+
]
|
hypern/application.py
ADDED
@@ -0,0 +1,495 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
import asyncio
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import Any, Callable, List, TypeVar
|
7
|
+
|
8
|
+
import orjson
|
9
|
+
import psutil
|
10
|
+
from typing_extensions import Annotated, Doc
|
11
|
+
|
12
|
+
from hypern.args_parser import ArgsConfig
|
13
|
+
from hypern.datastructures import Contact, HTTPMethod, Info, License
|
14
|
+
from hypern.hypern import DatabaseConfig, FunctionInfo, MiddlewareConfig, Router, Server, WebsocketRouter
|
15
|
+
from hypern.hypern import Route as InternalRoute
|
16
|
+
from hypern.logging import logger
|
17
|
+
from hypern.middleware import Middleware
|
18
|
+
from hypern.openapi import SchemaGenerator, SwaggerUI
|
19
|
+
from hypern.processpool import run_processes
|
20
|
+
from hypern.response import HTMLResponse, JSONResponse
|
21
|
+
from hypern.routing import Route
|
22
|
+
from hypern.scheduler import Scheduler
|
23
|
+
from hypern.ws import WebsocketRoute
|
24
|
+
|
25
|
+
AppType = TypeVar("AppType", bound="Hypern")
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class ThreadConfig:
|
30
|
+
workers: int
|
31
|
+
max_blocking_threads: int
|
32
|
+
|
33
|
+
|
34
|
+
class ThreadConfigurator:
|
35
|
+
def __init__(self):
|
36
|
+
self._cpu_count = psutil.cpu_count(logical=True)
|
37
|
+
self._memory_gb = psutil.virtual_memory().total / (1024**3)
|
38
|
+
|
39
|
+
def get_config(self, concurrent_requests: int = None) -> ThreadConfig:
|
40
|
+
"""Calculate optimal thread configuration based on system resources."""
|
41
|
+
workers = max(2, self._cpu_count)
|
42
|
+
|
43
|
+
if concurrent_requests:
|
44
|
+
max_blocking = min(max(32, concurrent_requests * 2), workers * 4, int(self._memory_gb * 8))
|
45
|
+
else:
|
46
|
+
max_blocking = min(workers * 4, int(self._memory_gb * 8), 256)
|
47
|
+
|
48
|
+
return ThreadConfig(workers=workers, max_blocking_threads=max_blocking)
|
49
|
+
|
50
|
+
|
51
|
+
class Hypern:
|
52
|
+
def __init__(
|
53
|
+
self: AppType,
|
54
|
+
routes: Annotated[
|
55
|
+
List[Route] | None,
|
56
|
+
Doc(
|
57
|
+
"""
|
58
|
+
A list of routes to serve incoming HTTP and WebSocket requests.
|
59
|
+
You can define routes using the `Route` class from `Hypern.routing`.
|
60
|
+
**Example**
|
61
|
+
---
|
62
|
+
```python
|
63
|
+
class DefaultRoute(HTTPEndpoint):
|
64
|
+
async def get(self, global_dependencies):
|
65
|
+
return PlainTextResponse("/hello")
|
66
|
+
Route("/test", DefaultRoute)
|
67
|
+
|
68
|
+
# Or you can define routes using the decorator
|
69
|
+
route = Route("/test)
|
70
|
+
@route.get("/route")
|
71
|
+
def def_get():
|
72
|
+
return PlainTextResponse("Hello")
|
73
|
+
```
|
74
|
+
"""
|
75
|
+
),
|
76
|
+
] = None,
|
77
|
+
websockets: Annotated[
|
78
|
+
List[WebsocketRoute] | None,
|
79
|
+
Doc(
|
80
|
+
"""
|
81
|
+
A list of routes to serve incoming WebSocket requests.
|
82
|
+
You can define routes using the `WebsocketRoute` class from `Hypern
|
83
|
+
"""
|
84
|
+
),
|
85
|
+
] = None,
|
86
|
+
dependencies: Annotated[
|
87
|
+
dict[str, Any] | None,
|
88
|
+
Doc(
|
89
|
+
"""
|
90
|
+
A dictionary of global dependencies that can be accessed by all routes.
|
91
|
+
"""
|
92
|
+
),
|
93
|
+
] = None,
|
94
|
+
title: Annotated[
|
95
|
+
str,
|
96
|
+
Doc(
|
97
|
+
"""
|
98
|
+
The title of the API.
|
99
|
+
|
100
|
+
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
101
|
+
|
102
|
+
Read more in the
|
103
|
+
"""
|
104
|
+
),
|
105
|
+
] = "Hypern",
|
106
|
+
summary: Annotated[
|
107
|
+
str | None,
|
108
|
+
Doc(
|
109
|
+
""""
|
110
|
+
A short summary of the API.
|
111
|
+
|
112
|
+
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
113
|
+
"""
|
114
|
+
),
|
115
|
+
] = None,
|
116
|
+
description: Annotated[
|
117
|
+
str,
|
118
|
+
Doc(
|
119
|
+
"""
|
120
|
+
A description of the API. Supports Markdown (using
|
121
|
+
[CommonMark syntax](https://commonmark.org/)).
|
122
|
+
|
123
|
+
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
124
|
+
"""
|
125
|
+
),
|
126
|
+
] = "",
|
127
|
+
version: Annotated[
|
128
|
+
str,
|
129
|
+
Doc(
|
130
|
+
"""
|
131
|
+
The version of the API.
|
132
|
+
|
133
|
+
**Note** This is the version of your application, not the version of
|
134
|
+
the OpenAPI specification nor the version of Application being used.
|
135
|
+
|
136
|
+
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
137
|
+
"""
|
138
|
+
),
|
139
|
+
] = "0.0.1",
|
140
|
+
contact: Annotated[
|
141
|
+
Contact | None,
|
142
|
+
Doc(
|
143
|
+
"""
|
144
|
+
A dictionary with the contact information for the exposed API.
|
145
|
+
|
146
|
+
It can contain several fields.
|
147
|
+
|
148
|
+
* `name`: (`str`) The name of the contact person/organization.
|
149
|
+
* `url`: (`str`) A URL pointing to the contact information. MUST be in
|
150
|
+
the format of a URL.
|
151
|
+
* `email`: (`str`) The email address of the contact person/organization.
|
152
|
+
MUST be in the format of an email address.
|
153
|
+
"""
|
154
|
+
),
|
155
|
+
] = None,
|
156
|
+
openapi_url: Annotated[
|
157
|
+
str | None,
|
158
|
+
Doc(
|
159
|
+
"""
|
160
|
+
The URL where the OpenAPI schema will be served from.
|
161
|
+
|
162
|
+
If you set it to `None`, no OpenAPI schema will be served publicly, and
|
163
|
+
the default automatic endpoints `/docs` and `/redoc` will also be
|
164
|
+
disabled.
|
165
|
+
"""
|
166
|
+
),
|
167
|
+
] = "/openapi.json",
|
168
|
+
docs_url: Annotated[
|
169
|
+
str | None,
|
170
|
+
Doc(
|
171
|
+
"""
|
172
|
+
The path to the automatic interactive API documentation.
|
173
|
+
It is handled in the browser by Swagger UI.
|
174
|
+
|
175
|
+
The default URL is `/docs`. You can disable it by setting it to `None`.
|
176
|
+
|
177
|
+
If `openapi_url` is set to `None`, this will be automatically disabled.
|
178
|
+
"""
|
179
|
+
),
|
180
|
+
] = "/docs",
|
181
|
+
license_info: Annotated[
|
182
|
+
License | None,
|
183
|
+
Doc(
|
184
|
+
"""
|
185
|
+
A dictionary with the license information for the exposed API.
|
186
|
+
|
187
|
+
It can contain several fields.
|
188
|
+
|
189
|
+
* `name`: (`str`) **REQUIRED** (if a `license_info` is set). The
|
190
|
+
license name used for the API.
|
191
|
+
* `identifier`: (`str`) An [SPDX](https://spdx.dev/) license expression
|
192
|
+
for the API. The `identifier` field is mutually exclusive of the `url`
|
193
|
+
field. Available since OpenAPI 3.1.0
|
194
|
+
* `url`: (`str`) A URL to the license used for the API. This MUST be
|
195
|
+
the format of a URL.
|
196
|
+
|
197
|
+
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
198
|
+
|
199
|
+
**Example**
|
200
|
+
|
201
|
+
```python
|
202
|
+
app = Hypern(
|
203
|
+
license_info={
|
204
|
+
"name": "Apache 2.0",
|
205
|
+
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
206
|
+
}
|
207
|
+
)
|
208
|
+
```
|
209
|
+
"""
|
210
|
+
),
|
211
|
+
] = None,
|
212
|
+
scheduler: Annotated[
|
213
|
+
Scheduler | None,
|
214
|
+
Doc(
|
215
|
+
"""
|
216
|
+
A scheduler to run background tasks.
|
217
|
+
"""
|
218
|
+
),
|
219
|
+
] = None,
|
220
|
+
database_config: Annotated[
|
221
|
+
DatabaseConfig | None,
|
222
|
+
Doc(
|
223
|
+
"""
|
224
|
+
The database configuration for the application.
|
225
|
+
"""
|
226
|
+
),
|
227
|
+
] = None,
|
228
|
+
*args: Any,
|
229
|
+
**kwargs: Any,
|
230
|
+
) -> None:
|
231
|
+
super().__init__(*args, **kwargs)
|
232
|
+
self.router = Router(path="/")
|
233
|
+
self.websocket_router = WebsocketRouter(path="/")
|
234
|
+
self.dependencies = dependencies or {}
|
235
|
+
self.scheduler = scheduler
|
236
|
+
self.middleware_before_request = []
|
237
|
+
self.middleware_after_request = []
|
238
|
+
self.response_headers = {}
|
239
|
+
self.args = ArgsConfig()
|
240
|
+
self.start_up_handler = None
|
241
|
+
self.shutdown_handler = None
|
242
|
+
self.database_config = database_config
|
243
|
+
self.thread_config = ThreadConfigurator().get_config()
|
244
|
+
|
245
|
+
for route in routes or []:
|
246
|
+
self.router.extend_route(route(app=self).routes)
|
247
|
+
|
248
|
+
for websocket_route in websockets or []:
|
249
|
+
for route in websocket_route.routes:
|
250
|
+
self.websocket_router.add_route(route)
|
251
|
+
|
252
|
+
if openapi_url and docs_url:
|
253
|
+
self.__add_openapi(
|
254
|
+
info=Info(
|
255
|
+
title=title,
|
256
|
+
summary=summary,
|
257
|
+
description=description,
|
258
|
+
version=version,
|
259
|
+
contact=contact,
|
260
|
+
license=license_info,
|
261
|
+
),
|
262
|
+
openapi_url=openapi_url,
|
263
|
+
docs_url=docs_url,
|
264
|
+
)
|
265
|
+
|
266
|
+
def __add_openapi(
|
267
|
+
self,
|
268
|
+
info: Info,
|
269
|
+
openapi_url: str,
|
270
|
+
docs_url: str,
|
271
|
+
):
|
272
|
+
"""
|
273
|
+
Adds OpenAPI schema and documentation routes to the application.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
info (Info): An instance of the Info class containing metadata about the API.
|
277
|
+
openapi_url (str): The URL path where the OpenAPI schema will be served.
|
278
|
+
docs_url (str): The URL path where the Swagger UI documentation will be served.
|
279
|
+
|
280
|
+
The method defines two internal functions:
|
281
|
+
- schema: Generates and returns the OpenAPI schema as a JSON response.
|
282
|
+
- template_render: Renders and returns the Swagger UI documentation as an HTML response.
|
283
|
+
|
284
|
+
The method then adds routes to the application for serving the OpenAPI schema and the Swagger UI documentation.
|
285
|
+
"""
|
286
|
+
|
287
|
+
def schema(*args, **kwargs):
|
288
|
+
schemas = SchemaGenerator(
|
289
|
+
{
|
290
|
+
"openapi": "3.0.0",
|
291
|
+
"info": info.model_dump(),
|
292
|
+
"components": {"securitySchemes": {}},
|
293
|
+
}
|
294
|
+
)
|
295
|
+
return JSONResponse(content=orjson.dumps(schemas.get_schema(self)))
|
296
|
+
|
297
|
+
def template_render(*args, **kwargs):
|
298
|
+
swagger = SwaggerUI(
|
299
|
+
title="Swagger",
|
300
|
+
openapi_url=openapi_url,
|
301
|
+
)
|
302
|
+
template = swagger.get_html_content()
|
303
|
+
return HTMLResponse(template)
|
304
|
+
|
305
|
+
self.add_route(HTTPMethod.GET, openapi_url, schema)
|
306
|
+
self.add_route(HTTPMethod.GET, docs_url, template_render)
|
307
|
+
|
308
|
+
def inject(self, key: str, value: Any):
|
309
|
+
"""
|
310
|
+
Injects a key-value pair into the injectables dictionary.
|
311
|
+
|
312
|
+
Args:
|
313
|
+
key (str): The key to be added to the injectables dictionary.
|
314
|
+
value (Any): The value to be associated with the key.
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
self: Returns the instance of the class to allow method chaining.
|
318
|
+
"""
|
319
|
+
self.dependencies[key] = value
|
320
|
+
return self
|
321
|
+
|
322
|
+
def add_response_header(self, key: str, value: str):
|
323
|
+
"""
|
324
|
+
Adds a response header to the response headers dictionary.
|
325
|
+
|
326
|
+
Args:
|
327
|
+
key (str): The header field name.
|
328
|
+
value (str): The header field value.
|
329
|
+
"""
|
330
|
+
self.response_headers[key] = value
|
331
|
+
|
332
|
+
def before_request(self):
|
333
|
+
"""
|
334
|
+
A decorator to register a function to be executed before each request.
|
335
|
+
|
336
|
+
This decorator can be used to add middleware functions that will be
|
337
|
+
executed before the main request handler. The function can be either
|
338
|
+
synchronous or asynchronous.
|
339
|
+
|
340
|
+
Returns:
|
341
|
+
function: The decorator function that registers the middleware.
|
342
|
+
"""
|
343
|
+
|
344
|
+
logger.warning("This functin will be deprecated in version 0.4.0. Please use the middleware class instead.")
|
345
|
+
|
346
|
+
def decorator(func):
|
347
|
+
is_async = asyncio.iscoroutinefunction(func)
|
348
|
+
func_info = FunctionInfo(handler=func, is_async=is_async)
|
349
|
+
self.middleware_before_request.append((func_info, MiddlewareConfig.default()))
|
350
|
+
return func
|
351
|
+
|
352
|
+
return decorator
|
353
|
+
|
354
|
+
def after_request(self):
|
355
|
+
"""
|
356
|
+
Decorator to register a function to be called after each request.
|
357
|
+
|
358
|
+
This decorator can be used to register both synchronous and asynchronous functions.
|
359
|
+
The registered function will be wrapped in a FunctionInfo object and appended to the
|
360
|
+
middleware_after_request list.
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
function: The decorator function that registers the given function.
|
364
|
+
"""
|
365
|
+
logger.warning("This functin will be deprecated in version 0.4.0. Please use the middleware class instead.")
|
366
|
+
|
367
|
+
def decorator(func):
|
368
|
+
is_async = asyncio.iscoroutinefunction(func)
|
369
|
+
func_info = FunctionInfo(handler=func, is_async=is_async)
|
370
|
+
self.middleware_after_request.append((func_info, MiddlewareConfig.default()))
|
371
|
+
return func
|
372
|
+
|
373
|
+
return decorator
|
374
|
+
|
375
|
+
def add_middleware(self, middleware: Middleware):
|
376
|
+
"""
|
377
|
+
Adds middleware to the application.
|
378
|
+
|
379
|
+
This method attaches the middleware to the application instance and registers
|
380
|
+
its `before_request` and `after_request` hooks if they are defined.
|
381
|
+
|
382
|
+
Args:
|
383
|
+
middleware (Middleware): The middleware instance to be added.
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
self: The application instance with the middleware added.
|
387
|
+
"""
|
388
|
+
setattr(middleware, "app", self)
|
389
|
+
before_request = getattr(middleware, "before_request", None)
|
390
|
+
after_request = getattr(middleware, "after_request", None)
|
391
|
+
|
392
|
+
is_async = asyncio.iscoroutinefunction(before_request)
|
393
|
+
before_request = FunctionInfo(handler=before_request, is_async=is_async)
|
394
|
+
self.middleware_before_request.append((before_request, middleware.config))
|
395
|
+
|
396
|
+
is_async = asyncio.iscoroutinefunction(after_request)
|
397
|
+
after_request = FunctionInfo(handler=after_request, is_async=is_async)
|
398
|
+
self.middleware_after_request.append((after_request, middleware.config))
|
399
|
+
|
400
|
+
def set_database_config(self, config: DatabaseConfig):
|
401
|
+
"""
|
402
|
+
Sets the database configuration for the application.
|
403
|
+
|
404
|
+
Args:
|
405
|
+
config (DatabaseConfig): The database configuration to be set.
|
406
|
+
"""
|
407
|
+
self.database_config = config
|
408
|
+
|
409
|
+
def start(
|
410
|
+
self,
|
411
|
+
):
|
412
|
+
"""
|
413
|
+
Starts the server with the specified configuration.
|
414
|
+
Raises:
|
415
|
+
ValueError: If an invalid port number is entered when prompted.
|
416
|
+
|
417
|
+
"""
|
418
|
+
if self.scheduler:
|
419
|
+
self.scheduler.start()
|
420
|
+
|
421
|
+
server = Server()
|
422
|
+
server.set_router(router=self.router)
|
423
|
+
server.set_websocket_router(websocket_router=self.websocket_router)
|
424
|
+
server.set_dependencies(dependencies=self.dependencies)
|
425
|
+
server.set_before_hooks(hooks=self.middleware_before_request)
|
426
|
+
server.set_after_hooks(hooks=self.middleware_after_request)
|
427
|
+
server.set_response_headers(headers=self.response_headers)
|
428
|
+
|
429
|
+
if self.database_config:
|
430
|
+
server.set_database_config(config=self.database_config)
|
431
|
+
if self.start_up_handler:
|
432
|
+
server.set_startup_handler(self.start_up_handler)
|
433
|
+
if self.shutdown_handler:
|
434
|
+
server.set_shutdown_handler(self.shutdown_handler)
|
435
|
+
|
436
|
+
if self.args.auto_workers:
|
437
|
+
self.args.workers = self.thread_config.workers
|
438
|
+
self.args.max_blocking_threads = self.thread_config.max_blocking_threads
|
439
|
+
|
440
|
+
if self.args.http2:
|
441
|
+
server.enable_http2()
|
442
|
+
|
443
|
+
run_processes(
|
444
|
+
server=server,
|
445
|
+
host=self.args.host,
|
446
|
+
port=self.args.port,
|
447
|
+
workers=self.args.workers,
|
448
|
+
processes=self.args.processes,
|
449
|
+
max_blocking_threads=self.args.max_blocking_threads,
|
450
|
+
reload=self.args.reload,
|
451
|
+
)
|
452
|
+
|
453
|
+
def add_route(self, method: HTTPMethod, endpoint: str, handler: Callable[..., Any]):
|
454
|
+
"""
|
455
|
+
Adds a route to the router.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
method (HTTPMethod): The HTTP method for the route (e.g., GET, POST).
|
459
|
+
endpoint (str): The endpoint path for the route.
|
460
|
+
handler (Callable[..., Any]): The function that handles requests to the route.
|
461
|
+
|
462
|
+
"""
|
463
|
+
is_async = asyncio.iscoroutinefunction(handler)
|
464
|
+
func_info = FunctionInfo(handler=handler, is_async=is_async)
|
465
|
+
route = InternalRoute(path=endpoint, function=func_info, method=method.name)
|
466
|
+
self.router.add_route(route=route)
|
467
|
+
|
468
|
+
def add_websocket(self, ws_route: WebsocketRoute):
|
469
|
+
"""
|
470
|
+
Adds a WebSocket route to the WebSocket router.
|
471
|
+
|
472
|
+
Args:
|
473
|
+
ws_route (WebsocketRoute): The WebSocket route to be added to the router.
|
474
|
+
"""
|
475
|
+
for route in ws_route.routes:
|
476
|
+
self.websocket_router.add_route(route=route)
|
477
|
+
|
478
|
+
def on_startup(self, handler: Callable[..., Any]):
|
479
|
+
"""
|
480
|
+
Registers a function to be executed on application startup.
|
481
|
+
|
482
|
+
Args:
|
483
|
+
handler (Callable[..., Any]): The function to be executed on application startup.
|
484
|
+
"""
|
485
|
+
# decorator
|
486
|
+
self.start_up_handler = FunctionInfo(handler=handler, is_async=asyncio.iscoroutinefunction(handler))
|
487
|
+
|
488
|
+
def on_shutdown(self, handler: Callable[..., Any]):
|
489
|
+
"""
|
490
|
+
Registers a function to be executed on application shutdown.
|
491
|
+
|
492
|
+
Args:
|
493
|
+
handler (Callable[..., Any]): The function to be executed on application shutdown.
|
494
|
+
"""
|
495
|
+
self.shutdown_handler = FunctionInfo(handler=handler, is_async=asyncio.iscoroutinefunction(handler))
|
hypern/args_parser.py
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import argparse
|
2
|
+
|
3
|
+
|
4
|
+
class ArgsConfig:
|
5
|
+
def __init__(self) -> None:
|
6
|
+
parser = argparse.ArgumentParser(description="Hypern: A Versatile Python and Rust Framework")
|
7
|
+
self.parser = parser
|
8
|
+
parser.add_argument(
|
9
|
+
"--host",
|
10
|
+
type=str,
|
11
|
+
default=None,
|
12
|
+
required=False,
|
13
|
+
help="Choose the host. [Defaults to `127.0.0.1`]",
|
14
|
+
)
|
15
|
+
|
16
|
+
parser.add_argument(
|
17
|
+
"--port",
|
18
|
+
type=int,
|
19
|
+
default=None,
|
20
|
+
required=False,
|
21
|
+
help="Choose the port. [Defaults to `5000`]",
|
22
|
+
)
|
23
|
+
|
24
|
+
parser.add_argument(
|
25
|
+
"--processes",
|
26
|
+
type=int,
|
27
|
+
default=None,
|
28
|
+
required=False,
|
29
|
+
help="Choose the number of processes. [Default: 1]",
|
30
|
+
)
|
31
|
+
parser.add_argument(
|
32
|
+
"--workers",
|
33
|
+
type=int,
|
34
|
+
default=None,
|
35
|
+
required=False,
|
36
|
+
help="Choose the number of workers. [Default: 1]",
|
37
|
+
)
|
38
|
+
|
39
|
+
parser.add_argument(
|
40
|
+
"--max-blocking-threads",
|
41
|
+
type=int,
|
42
|
+
default=None,
|
43
|
+
required=False,
|
44
|
+
help="Choose the maximum number of blocking threads. [Default: 100]",
|
45
|
+
)
|
46
|
+
|
47
|
+
parser.add_argument(
|
48
|
+
"--reload",
|
49
|
+
action="store_true",
|
50
|
+
help="It restarts the server based on file changes.",
|
51
|
+
)
|
52
|
+
parser.add_argument(
|
53
|
+
"--auto-workers",
|
54
|
+
action="store_true",
|
55
|
+
help="It sets the number of workers and max-blocking-threads automatically.",
|
56
|
+
)
|
57
|
+
|
58
|
+
parser.add_argument(
|
59
|
+
"--http2",
|
60
|
+
action="store_true",
|
61
|
+
help="Enable HTTP/2 support.",
|
62
|
+
)
|
63
|
+
|
64
|
+
args, _ = parser.parse_known_args()
|
65
|
+
|
66
|
+
self.host = args.host or "127.0.0.1"
|
67
|
+
self.port = args.port or 5000
|
68
|
+
self.max_blocking_threads = args.max_blocking_threads or 32
|
69
|
+
self.processes = args.processes or 1
|
70
|
+
self.workers = args.workers or 1
|
71
|
+
self.reload = args.reload or False
|
72
|
+
self.auto_workers = args.auto_workers
|
73
|
+
self.http2 = args.http2 or False
|
hypern/auth/__init__.py
ADDED
File without changes
|
hypern/background.py
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
from .backend import BaseBackend
|
2
|
+
from .redis_backend import RedisBackend
|
3
|
+
|
4
|
+
from .strategies import CacheAsideStrategy, CacheEntry, CacheStrategy, StaleWhileRevalidateStrategy, cache_with_strategy
|
5
|
+
|
6
|
+
__all__ = ["BaseBackend", "RedisBackend", "CacheAsideStrategy", "CacheEntry", "CacheStrategy", "StaleWhileRevalidateStrategy", "cache_with_strategy"]
|
@@ -0,0 +1,31 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Any, Optional
|
3
|
+
|
4
|
+
|
5
|
+
class BaseBackend(ABC):
|
6
|
+
@abstractmethod
|
7
|
+
async def get(self, key: str) -> Any: ...
|
8
|
+
|
9
|
+
@abstractmethod
|
10
|
+
async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None: ...
|
11
|
+
|
12
|
+
@abstractmethod
|
13
|
+
async def delete_pattern(self, pattern: str) -> None: ...
|
14
|
+
|
15
|
+
@abstractmethod
|
16
|
+
async def delete(self, key: str) -> None: ...
|
17
|
+
|
18
|
+
@abstractmethod
|
19
|
+
async def exists(self, key: str) -> bool: ...
|
20
|
+
|
21
|
+
@abstractmethod
|
22
|
+
async def set_nx(self, key: str, value: Any, ttl: Optional[int] = None) -> bool: ...
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
async def ttl(self, key: str) -> int: ...
|
26
|
+
|
27
|
+
@abstractmethod
|
28
|
+
async def incr(self, key: str) -> int: ...
|
29
|
+
|
30
|
+
@abstractmethod
|
31
|
+
async def clear(self) -> None: ...
|