hypern 0.3.7__cp312-cp312-win32.whl → 0.3.8__cp312-cp312-win32.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 +21 -1
- hypern/application.py +26 -36
- hypern/args_parser.py +0 -26
- hypern/datastructures.py +2 -2
- hypern/hypern.cp312-win32.pyd +0 -0
- hypern/hypern.pyi +2 -5
- hypern/worker.py +265 -21
- {hypern-0.3.7.dist-info → hypern-0.3.8.dist-info}/METADATA +15 -6
- {hypern-0.3.7.dist-info → hypern-0.3.8.dist-info}/RECORD +11 -12
- {hypern-0.3.7.dist-info → hypern-0.3.8.dist-info}/WHEEL +1 -1
- hypern/ws.py +0 -16
- {hypern-0.3.7.dist-info → hypern-0.3.8.dist-info}/licenses/LICENSE +0 -0
hypern/__init__.py
CHANGED
@@ -1,4 +1,24 @@
|
|
1
|
+
from hypern.logging import logger
|
2
|
+
from hypern.routing import HTTPEndpoint, QueuedHTTPEndpoint, Route
|
3
|
+
from hypern.ws import WebsocketRoute, WebSocketSession
|
4
|
+
|
1
5
|
from .application import Hypern
|
2
6
|
from .hypern import Request, Response
|
7
|
+
from .response import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse, RedirectResponse
|
3
8
|
|
4
|
-
__all__ = [
|
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
CHANGED
@@ -83,6 +83,14 @@ class Hypern:
|
|
83
83
|
"""
|
84
84
|
),
|
85
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,
|
86
94
|
title: Annotated[
|
87
95
|
str,
|
88
96
|
Doc(
|
@@ -209,22 +217,6 @@ class Hypern:
|
|
209
217
|
"""
|
210
218
|
),
|
211
219
|
] = None,
|
212
|
-
default_injectables: Annotated[
|
213
|
-
dict[str, Any] | None,
|
214
|
-
Doc(
|
215
|
-
"""
|
216
|
-
A dictionary of default injectables to be passed to all routes.
|
217
|
-
"""
|
218
|
-
),
|
219
|
-
] = None,
|
220
|
-
auto_compression: Annotated[
|
221
|
-
bool,
|
222
|
-
Doc(
|
223
|
-
"""
|
224
|
-
Enable automatic compression of responses.
|
225
|
-
"""
|
226
|
-
),
|
227
|
-
] = False,
|
228
220
|
database_config: Annotated[
|
229
221
|
DatabaseConfig | None,
|
230
222
|
Doc(
|
@@ -239,15 +231,14 @@ class Hypern:
|
|
239
231
|
super().__init__(*args, **kwargs)
|
240
232
|
self.router = Router(path="/")
|
241
233
|
self.websocket_router = WebsocketRouter(path="/")
|
234
|
+
self.dependencies = dependencies or {}
|
242
235
|
self.scheduler = scheduler
|
243
|
-
self.injectables = default_injectables or {}
|
244
236
|
self.middleware_before_request = []
|
245
237
|
self.middleware_after_request = []
|
246
238
|
self.response_headers = {}
|
247
239
|
self.args = ArgsConfig()
|
248
240
|
self.start_up_handler = None
|
249
241
|
self.shutdown_handler = None
|
250
|
-
self.auto_compression = auto_compression
|
251
242
|
self.database_config = database_config
|
252
243
|
self.thread_config = ThreadConfigurator().get_config()
|
253
244
|
|
@@ -255,7 +246,8 @@ class Hypern:
|
|
255
246
|
self.router.extend_route(route(app=self).routes)
|
256
247
|
|
257
248
|
for websocket_route in websockets or []:
|
258
|
-
|
249
|
+
for route in websocket_route.routes:
|
250
|
+
self.websocket_router.add_route(route)
|
259
251
|
|
260
252
|
if openapi_url and docs_url:
|
261
253
|
self.__add_openapi(
|
@@ -313,6 +305,20 @@ class Hypern:
|
|
313
305
|
self.add_route(HTTPMethod.GET, openapi_url, schema)
|
314
306
|
self.add_route(HTTPMethod.GET, docs_url, template_render)
|
315
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
|
+
|
316
322
|
def add_response_header(self, key: str, value: str):
|
317
323
|
"""
|
318
324
|
Adds a response header to the response headers dictionary.
|
@@ -366,20 +372,6 @@ class Hypern:
|
|
366
372
|
|
367
373
|
return decorator
|
368
374
|
|
369
|
-
def inject(self, key: str, value: Any):
|
370
|
-
"""
|
371
|
-
Injects a key-value pair into the injectables dictionary.
|
372
|
-
|
373
|
-
Args:
|
374
|
-
key (str): The key to be added to the injectables dictionary.
|
375
|
-
value (Any): The value to be associated with the key.
|
376
|
-
|
377
|
-
Returns:
|
378
|
-
self: Returns the instance of the class to allow method chaining.
|
379
|
-
"""
|
380
|
-
self.injectables[key] = value
|
381
|
-
return self
|
382
|
-
|
383
375
|
def add_middleware(self, middleware: Middleware):
|
384
376
|
"""
|
385
377
|
Adds middleware to the application.
|
@@ -429,12 +421,10 @@ class Hypern:
|
|
429
421
|
server = Server()
|
430
422
|
server.set_router(router=self.router)
|
431
423
|
server.set_websocket_router(websocket_router=self.websocket_router)
|
432
|
-
server.
|
424
|
+
server.set_dependencies(dependencies=self.dependencies)
|
433
425
|
server.set_before_hooks(hooks=self.middleware_before_request)
|
434
426
|
server.set_after_hooks(hooks=self.middleware_after_request)
|
435
427
|
server.set_response_headers(headers=self.response_headers)
|
436
|
-
server.set_auto_compression(enabled=self.auto_compression)
|
437
|
-
server.set_mem_pool_capacity(min_capacity=self.args.min_capacity, max_capacity=self.args.max_capacity)
|
438
428
|
|
439
429
|
if self.database_config:
|
440
430
|
server.set_database_config(config=self.database_config)
|
hypern/args_parser.py
CHANGED
@@ -49,35 +49,12 @@ class ArgsConfig:
|
|
49
49
|
action="store_true",
|
50
50
|
help="It restarts the server based on file changes.",
|
51
51
|
)
|
52
|
-
|
53
|
-
parser.add_argument(
|
54
|
-
"--auto-compression",
|
55
|
-
action="store_true",
|
56
|
-
help="It compresses the response automatically.",
|
57
|
-
)
|
58
|
-
|
59
52
|
parser.add_argument(
|
60
53
|
"--auto-workers",
|
61
54
|
action="store_true",
|
62
55
|
help="It sets the number of workers and max-blocking-threads automatically.",
|
63
56
|
)
|
64
57
|
|
65
|
-
parser.add_argument(
|
66
|
-
"--min-capacity",
|
67
|
-
type=int,
|
68
|
-
default=1,
|
69
|
-
required=False,
|
70
|
-
help="Choose the minimum memory pool capacity. [Default: 1]",
|
71
|
-
)
|
72
|
-
|
73
|
-
parser.add_argument(
|
74
|
-
"--max-capacity",
|
75
|
-
type=int,
|
76
|
-
default=100,
|
77
|
-
required=False,
|
78
|
-
help="Choose the maximum memory pool capacity. [Default: 100]",
|
79
|
-
)
|
80
|
-
|
81
58
|
args, _ = parser.parse_known_args()
|
82
59
|
|
83
60
|
self.host = args.host or "127.0.0.1"
|
@@ -86,7 +63,4 @@ class ArgsConfig:
|
|
86
63
|
self.processes = args.processes or 1
|
87
64
|
self.workers = args.workers or 1
|
88
65
|
self.reload = args.reload or False
|
89
|
-
self.auto_compression = args.auto_compression
|
90
66
|
self.auto_workers = args.auto_workers
|
91
|
-
self.min_capacity = args.min_capacity
|
92
|
-
self.max_capacity = args.max_capacity
|
hypern/datastructures.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
from enum import Enum
|
3
|
-
from pydantic import BaseModel, AnyUrl
|
3
|
+
from pydantic import BaseModel, AnyUrl
|
4
4
|
|
5
5
|
|
6
6
|
class BaseModelWithConfig(BaseModel):
|
@@ -10,7 +10,7 @@ class BaseModelWithConfig(BaseModel):
|
|
10
10
|
class Contact(BaseModelWithConfig):
|
11
11
|
name: Optional[str] = None
|
12
12
|
url: Optional[AnyUrl] = None
|
13
|
-
email: Optional[
|
13
|
+
email: Optional[str] = None
|
14
14
|
|
15
15
|
|
16
16
|
class License(BaseModelWithConfig):
|
hypern/hypern.cp312-win32.pyd
CHANGED
Binary file
|
hypern/hypern.pyi
CHANGED
@@ -181,16 +181,13 @@ class Server:
|
|
181
181
|
def set_router(self, router: Router) -> None: ...
|
182
182
|
def set_websocket_router(self, websocket_router: WebsocketRouter) -> None: ...
|
183
183
|
def start(self, socket: SocketHeld, worker: int, max_blocking_threads: int) -> None: ...
|
184
|
-
def inject(self, key: str, value: Any) -> None: ...
|
185
|
-
def set_injected(self, injected: Dict[str, Any]) -> None: ...
|
186
184
|
def set_before_hooks(self, hooks: List[FunctionInfo]) -> None: ...
|
187
185
|
def set_after_hooks(self, hooks: List[FunctionInfo]) -> None: ...
|
188
186
|
def set_response_headers(self, headers: Dict[str, str]) -> None: ...
|
189
187
|
def set_startup_handler(self, on_startup: FunctionInfo) -> None: ...
|
190
188
|
def set_shutdown_handler(self, on_shutdown: FunctionInfo) -> None: ...
|
191
|
-
def set_auto_compression(self, enabled: bool) -> None: ...
|
192
189
|
def set_database_config(self, config: DatabaseConfig) -> None: ...
|
193
|
-
def
|
190
|
+
def set_dependencies(self, dependencies: Dict[str, Any]) -> None: ...
|
194
191
|
|
195
192
|
class Route:
|
196
193
|
path: str
|
@@ -278,7 +275,7 @@ class UploadedFile:
|
|
278
275
|
path: str
|
279
276
|
size: int
|
280
277
|
content: bytes
|
281
|
-
|
278
|
+
file_name: str
|
282
279
|
|
283
280
|
@dataclass
|
284
281
|
class BodyData:
|
hypern/worker.py
CHANGED
@@ -1,30 +1,274 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
import traceback
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
7
|
+
from functools import partial, wraps
|
8
|
+
from typing import Callable, Dict, List
|
9
|
+
|
3
10
|
from celery import Celery
|
4
|
-
from
|
11
|
+
from celery.result import AsyncResult
|
12
|
+
from celery.signals import (
|
13
|
+
after_setup_logger,
|
14
|
+
after_setup_task_logger,
|
15
|
+
task_failure,
|
16
|
+
task_postrun,
|
17
|
+
task_prerun,
|
18
|
+
worker_ready,
|
19
|
+
worker_shutdown,
|
20
|
+
)
|
21
|
+
from kombu import Exchange, Queue
|
22
|
+
|
23
|
+
|
24
|
+
class Worker(Celery):
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
main: str = None,
|
28
|
+
broker_url: str = None,
|
29
|
+
result_backend: str = "rpc://",
|
30
|
+
queues: Dict[str, Dict] = None,
|
31
|
+
task_routes: Dict[str, str] = None,
|
32
|
+
imports: List[str] = None,
|
33
|
+
**kwargs,
|
34
|
+
):
|
35
|
+
super().__init__(main, **kwargs)
|
36
|
+
|
37
|
+
self._executor = ThreadPoolExecutor()
|
38
|
+
self._task_timings = {}
|
39
|
+
|
40
|
+
self.default_exchange = Exchange("default", type="direct")
|
41
|
+
self.priority_exchange = Exchange("priority", type="direct")
|
42
|
+
|
43
|
+
default_queues = {
|
44
|
+
"default": {"exchange": self.default_exchange, "routing_key": "default"},
|
45
|
+
"high_priority": {"exchange": self.priority_exchange, "routing_key": "high"},
|
46
|
+
"low_priority": {"exchange": self.priority_exchange, "routing_key": "low"},
|
47
|
+
}
|
48
|
+
if queues:
|
49
|
+
default_queues.update(queues)
|
50
|
+
|
51
|
+
self._queues = {
|
52
|
+
name: Queue(
|
53
|
+
name,
|
54
|
+
exchange=config.get("exchange", self.default_exchange),
|
55
|
+
routing_key=config.get("routing_key", name),
|
56
|
+
queue_arguments=config.get("arguments", {}),
|
57
|
+
)
|
58
|
+
for name, config in default_queues.items()
|
59
|
+
}
|
60
|
+
|
61
|
+
self.conf.update(
|
62
|
+
broker_url=broker_url,
|
63
|
+
result_backend=result_backend,
|
64
|
+
# Worker Pool Configuration
|
65
|
+
worker_pool="solo",
|
66
|
+
worker_pool_restarts=True,
|
67
|
+
broker_connection_retry_on_startup=True,
|
68
|
+
# Worker Configuration
|
69
|
+
worker_prefetch_multiplier=1,
|
70
|
+
worker_max_tasks_per_child=1000,
|
71
|
+
worker_concurrency=os.cpu_count(),
|
72
|
+
# Task Settings
|
73
|
+
task_acks_late=True,
|
74
|
+
task_reject_on_worker_lost=True,
|
75
|
+
task_time_limit=3600,
|
76
|
+
task_soft_time_limit=3000,
|
77
|
+
task_default_retry_delay=300,
|
78
|
+
task_max_retries=3,
|
79
|
+
# Memory Management
|
80
|
+
worker_max_memory_per_child=200000, # 200MB
|
81
|
+
# Task Routing
|
82
|
+
task_routes=task_routes,
|
83
|
+
task_queues=list(self._queues.values()),
|
84
|
+
# Performance Settings
|
85
|
+
task_compression="gzip",
|
86
|
+
result_compression="gzip",
|
87
|
+
task_serializer="json",
|
88
|
+
result_serializer="json",
|
89
|
+
accept_content=["json"],
|
90
|
+
imports=imports,
|
91
|
+
task_default_exchange=self.default_exchange.name,
|
92
|
+
task_default_routing_key="default",
|
93
|
+
)
|
94
|
+
|
95
|
+
self._setup_signals()
|
96
|
+
|
97
|
+
def _setup_signals(self):
|
98
|
+
@worker_ready.connect
|
99
|
+
def on_worker_ready(sender, **kwargs):
|
100
|
+
self.logger.info(f"Worker {sender.hostname} is ready")
|
101
|
+
|
102
|
+
@worker_shutdown.connect
|
103
|
+
def on_worker_shutdown(sender, **kwargs):
|
104
|
+
self.logger.info(f"Worker {sender.hostname} is shutting down")
|
105
|
+
self._executor.shutdown(wait=True)
|
106
|
+
|
107
|
+
@task_prerun.connect
|
108
|
+
def task_prerun_handler(task_id, task, *args, **kwargs):
|
109
|
+
self._task_timings[task_id] = {"start": time.time()}
|
110
|
+
self.logger.info(f"Task {task.name}[{task_id}] started")
|
111
|
+
|
112
|
+
@task_postrun.connect
|
113
|
+
def task_postrun_handler(task_id, task, *args, retval=None, **kwargs):
|
114
|
+
if task_id in self._task_timings:
|
115
|
+
start_time = self._task_timings[task_id]["start"]
|
116
|
+
duration = time.time() - start_time
|
117
|
+
self.logger.info(f"Task {task.name}[{task_id}] completed in {duration:.2f}s")
|
118
|
+
del self._task_timings[task_id]
|
119
|
+
|
120
|
+
@task_failure.connect
|
121
|
+
def task_failure_handler(task_id, exc, task, *args, **kwargs):
|
122
|
+
self.logger.error(f"Task {task.name}[{task_id}] failed: {exc}\n{traceback.format_exc()}")
|
123
|
+
|
124
|
+
@after_setup_logger.connect
|
125
|
+
def setup_celery_logger(logger, *args, **kwargs):
|
126
|
+
existing_logger = logging.getLogger("hypern")
|
127
|
+
logger.handlers = existing_logger.handlers
|
128
|
+
logger.filters = existing_logger.filters
|
129
|
+
logger.level = existing_logger.level
|
130
|
+
|
131
|
+
@after_setup_task_logger.connect
|
132
|
+
def setup_task_logger(logger, *args, **kwargs):
|
133
|
+
existing_logger = logging.getLogger("hypern")
|
134
|
+
logger.handlers = existing_logger.handlers
|
135
|
+
logger.filters = existing_logger.filters
|
136
|
+
logger.level = existing_logger.level
|
137
|
+
|
138
|
+
def add_task_routes(self, routes: Dict[str, str]) -> None:
|
139
|
+
"""
|
140
|
+
Example:
|
141
|
+
app.add_task_routes({
|
142
|
+
'tasks.email.*': 'email_queue',
|
143
|
+
'tasks.payment.process': 'payment_queue',
|
144
|
+
'tasks.high_priority.*': 'high_priority'
|
145
|
+
})
|
146
|
+
"""
|
147
|
+
for task_pattern, queue in routes.items():
|
148
|
+
self.add_task_route(task_pattern, queue)
|
149
|
+
|
150
|
+
def add_task_route(self, task_pattern: str, queue: str) -> None:
|
151
|
+
"""
|
152
|
+
Add a task route to the Celery app
|
153
|
+
|
154
|
+
Example:
|
155
|
+
app.add_task_route('tasks.email.send', 'email_queue')
|
156
|
+
app.add_task_route('tasks.payment.*', 'payment_queue')
|
157
|
+
"""
|
158
|
+
if queue not in self._queues:
|
159
|
+
raise ValueError(f"Queue '{queue}' does not exist. Create it first using create_queue()")
|
160
|
+
|
161
|
+
self._task_route_mapping[task_pattern] = queue
|
162
|
+
|
163
|
+
# Update Celery task routes
|
164
|
+
routes = self.conf.task_routes or {}
|
165
|
+
routes[task_pattern] = {"queue": queue}
|
166
|
+
self.conf.task_routes = routes
|
167
|
+
|
168
|
+
self.logger.info(f"Added route: {task_pattern} -> {queue}")
|
169
|
+
|
170
|
+
def task(self, *args, **opts):
|
171
|
+
"""
|
172
|
+
Decorator modified to support sync and async functions
|
173
|
+
"""
|
174
|
+
base_task = Celery.task.__get__(self)
|
175
|
+
|
176
|
+
def decorator(func):
|
177
|
+
is_async = asyncio.iscoroutinefunction(func)
|
178
|
+
|
179
|
+
if is_async:
|
180
|
+
|
181
|
+
@wraps(func)
|
182
|
+
async def async_wrapper(*fargs, **fkwargs):
|
183
|
+
return await func(*fargs, **fkwargs)
|
184
|
+
|
185
|
+
@base_task(*args, **opts)
|
186
|
+
def wrapped(*fargs, **fkwargs):
|
187
|
+
loop = asyncio.new_event_loop()
|
188
|
+
asyncio.set_event_loop(loop)
|
189
|
+
try:
|
190
|
+
return loop.run_until_complete(async_wrapper(*fargs, **fkwargs))
|
191
|
+
finally:
|
192
|
+
loop.close()
|
193
|
+
|
194
|
+
return wrapped
|
195
|
+
else:
|
196
|
+
return base_task(*args, **opts)(func)
|
197
|
+
|
198
|
+
return decorator
|
199
|
+
|
200
|
+
async def async_send_task(self, task_name: str, *args, **kwargs) -> AsyncResult:
|
201
|
+
"""
|
202
|
+
Version of send_task() that is async
|
203
|
+
"""
|
204
|
+
loop = asyncio.get_event_loop()
|
205
|
+
return await loop.run_in_executor(self._executor, partial(self.send_task, task_name, args=args, kwargs=kwargs))
|
206
|
+
|
207
|
+
async def async_result(self, task_id: str) -> Dict:
|
208
|
+
"""
|
209
|
+
Get the result of a task asynchronously
|
210
|
+
"""
|
211
|
+
async_result = self.AsyncResult(task_id)
|
212
|
+
loop = asyncio.get_event_loop()
|
213
|
+
|
214
|
+
result = await loop.run_in_executor(
|
215
|
+
self._executor,
|
216
|
+
lambda: {
|
217
|
+
"task_id": task_id,
|
218
|
+
"status": async_result.status,
|
219
|
+
"result": async_result.result,
|
220
|
+
"traceback": async_result.traceback,
|
221
|
+
"date_done": async_result.date_done,
|
222
|
+
},
|
223
|
+
)
|
224
|
+
return result
|
225
|
+
|
226
|
+
def get_queue_length(self, queue_name: str) -> int:
|
227
|
+
"""
|
228
|
+
Get the number of messages in a queue
|
229
|
+
"""
|
230
|
+
with self.connection_or_acquire() as conn:
|
231
|
+
channel = conn.channel()
|
232
|
+
queue = Queue(queue_name, channel=channel)
|
233
|
+
return queue.queue_declare(passive=True).message_count
|
5
234
|
|
235
|
+
async def chain_tasks(self, tasks: list) -> AsyncResult:
|
236
|
+
"""
|
237
|
+
Function to chain multiple tasks together
|
238
|
+
"""
|
239
|
+
chain = tasks[0]
|
240
|
+
for task in tasks[1:]:
|
241
|
+
chain = chain | task
|
242
|
+
return await self.adelay_task(chain)
|
6
243
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
return cls.instance
|
244
|
+
def register_task_middleware(self, middleware: Callable):
|
245
|
+
"""
|
246
|
+
Register a middleware function to be called before each task
|
247
|
+
"""
|
12
248
|
|
13
|
-
|
14
|
-
|
15
|
-
|
249
|
+
def task_middleware(task):
|
250
|
+
@wraps(task)
|
251
|
+
def _wrapped(*args, **kwargs):
|
252
|
+
return middleware(task, *args, **kwargs)
|
16
253
|
|
17
|
-
|
18
|
-
TaskBase = self.Task
|
254
|
+
return _wrapped
|
19
255
|
|
20
|
-
|
21
|
-
abstract = True
|
256
|
+
self.task = task_middleware(self.task)
|
22
257
|
|
23
|
-
|
24
|
-
|
25
|
-
|
258
|
+
def monitor_task(self, task_id: str) -> dict:
|
259
|
+
"""
|
260
|
+
Get monitoring data for a task
|
261
|
+
"""
|
262
|
+
result = self.AsyncResult(task_id)
|
263
|
+
timing_info = self._task_timings.get(task_id, {})
|
26
264
|
|
27
|
-
|
28
|
-
|
265
|
+
monitoring_data = {
|
266
|
+
"task_id": task_id,
|
267
|
+
"status": result.status,
|
268
|
+
"start_time": timing_info.get("start"),
|
269
|
+
"duration": time.time() - timing_info["start"] if timing_info.get("start") else None,
|
270
|
+
"result": result.result if result.ready() else None,
|
271
|
+
"traceback": result.traceback,
|
272
|
+
}
|
29
273
|
|
30
|
-
|
274
|
+
return monitoring_data
|
@@ -1,11 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hypern
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.8
|
4
4
|
Classifier: Programming Language :: Rust
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
7
7
|
Requires-Dist: sqlalchemy[asyncio] ==2.0.31
|
8
|
-
Requires-Dist: pydantic[email] ==2.8.2
|
9
8
|
Requires-Dist: passlib ==1.7.4
|
10
9
|
Requires-Dist: pyjwt ==2.8.0
|
11
10
|
Requires-Dist: pydash ==8.0.3
|
@@ -38,7 +37,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
38
37
|
|
39
38
|
Hypern: A Versatile Python and Rust Framework
|
40
39
|
|
41
|
-
Hypern is a flexible, open-source framework built on the [
|
40
|
+
Hypern is a flexible, open-source framework built on the [Rust](https://github.com/rust-lang/rust), designed to jumpstart your high-performance web development endeavors. By providing a pre-configured structure and essential components, Hypern empowers you to rapidly develop custom web applications that leverage the combined power of Python and Rust.
|
42
41
|
|
43
42
|
With Hypern, you can seamlessly integrate asynchronous features and build scalable solutions for RESTful APIs and dynamic web applications. Its intuitive design and robust tooling allow developers to focus on creating high-quality code while maximizing performance. Embrace the synergy of Python and Rust to elevate your web development experience.
|
44
43
|
|
@@ -92,7 +91,7 @@ routing = [
|
|
92
91
|
app = Hypern(routing)
|
93
92
|
|
94
93
|
if __name__ == "__main__":
|
95
|
-
app.start(
|
94
|
+
app.start()
|
96
95
|
```
|
97
96
|
|
98
97
|
```
|
@@ -100,6 +99,16 @@ $ python3 main.py
|
|
100
99
|
```
|
101
100
|
You can open swagger UI at path `/docs`
|
102
101
|
|
102
|
+
## CLI
|
103
|
+
|
104
|
+
- host (str): The host address to bind to. Defaults to '127.0.0.1'.
|
105
|
+
- port (int): The port number to bind to. Defaults to 5000.
|
106
|
+
- processes (int): The number of processes to use. Defaults to 1.
|
107
|
+
- workers (int): The number of worker threads to use. Defaults to 1.
|
108
|
+
- max_blocking_threads (int): The maximum number of blocking threads. Defaults to 32.
|
109
|
+
- reload (bool): If True, the server will restart on file changes.
|
110
|
+
- auto_workers (bool): If True, sets the number of workers and max-blocking-threads automatically.
|
111
|
+
|
103
112
|
|
104
113
|
## 💡 Features
|
105
114
|
|
@@ -107,7 +116,7 @@ You can open swagger UI at path `/docs`
|
|
107
116
|
- Rust-powered core with Python flexibility
|
108
117
|
- Multi-process architecture for optimal CPU utilization
|
109
118
|
- Async/await support for non-blocking operations
|
110
|
-
- Built on top of production-ready
|
119
|
+
- Built on top of production-ready Rust language
|
111
120
|
|
112
121
|
### 🛠 Development Experience
|
113
122
|
- Type hints and IDE support
|
@@ -118,7 +127,7 @@ You can open swagger UI at path `/docs`
|
|
118
127
|
### 🔌 Integration & Extensions
|
119
128
|
- Easy dependency injection
|
120
129
|
- Middleware support (before/after request hooks)
|
121
|
-
- WebSocket support
|
130
|
+
- WebSocket support
|
122
131
|
- Background task scheduling
|
123
132
|
- File upload handling
|
124
133
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
hypern-0.3.
|
2
|
-
hypern-0.3.
|
3
|
-
hypern-0.3.
|
4
|
-
hypern/application.py,sha256=
|
5
|
-
hypern/args_parser.py,sha256=
|
1
|
+
hypern-0.3.8.dist-info/METADATA,sha256=0bMLw-pA0VeoGBbQxXr4cqCO8dS6esga5NjluEeV5MY,4356
|
2
|
+
hypern-0.3.8.dist-info/WHEEL,sha256=19xj5Waw2omQTyAh5Pnrm7rXeZkfzX1OuBYghlKYN-I,92
|
3
|
+
hypern-0.3.8.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
|
4
|
+
hypern/application.py,sha256=07QGDd3w7tQRL8njfbNVinG6l8W7O0V-rN_SkO5O3B4,18002
|
5
|
+
hypern/args_parser.py,sha256=BjGCooHdZjOpyN-AGL9PCKCKdzROVThE3dzQgC97rng,2035
|
6
6
|
hypern/auth/authorization.py,sha256=-NprZsI0np889ZN1fp-MiVFrPoMNzUtatBJaCMtkllM,32
|
7
7
|
hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
hypern/background.py,sha256=xy38nQZSJsYFRXr3-uFJeNW9E1GiXXOC7lSe5pC0eyE,124
|
@@ -37,7 +37,7 @@ hypern/database/sql/model.py,sha256=C8_rJA1Adw1yPWthjmAGh26hjTBuwwlEdtH45ADxvL0,
|
|
37
37
|
hypern/database/sql/query.py,sha256=En19t27zt6iUDQbFgO_wLEWPQCkPeBuH3s37fzlhMVc,33345
|
38
38
|
hypern/database/sql/__init__.py,sha256=dbSAz2nP0DPKK4Bb_jJdObSaSYQfgZ8D4U1TJdc4e7c,645
|
39
39
|
hypern/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
hypern/datastructures.py,sha256=
|
40
|
+
hypern/datastructures.py,sha256=Pxr9KsZZTFfp0KC1-A4v5AkQfmrUyvVwxKuToQUOLoE,882
|
41
41
|
hypern/enum.py,sha256=KcVziJj7vWvyie0r2rtxhrLzdtkZAsf0DY58oJ4tQl4,360
|
42
42
|
hypern/exceptions/base.py,sha256=5AgfyEea79JjKk5MeAIJ-wy44FG5XEU0Jn3KXKScPiI,2017
|
43
43
|
hypern/exceptions/common.py,sha256=0E8wHRRTWjYOmtOCkTDvZ5NMwL6vRW6aiDD9X1eYA30,227
|
@@ -50,7 +50,7 @@ hypern/gateway/gateway.py,sha256=26K2qvJUR-0JnN4IlhwvSSt7EYcpYrBVDuzZ1ivQQ34,147
|
|
50
50
|
hypern/gateway/proxy.py,sha256=w1wcTplDnVrfjn7hb0M0yBVth5TGl88irF-MUYHysQQ,2463
|
51
51
|
hypern/gateway/service.py,sha256=PkRaM08olqM_j_4wRjEJCR8X8ZysAF2WOcfhWjaX2eo,1701
|
52
52
|
hypern/gateway/__init__.py,sha256=TpFWtqnJerW1-jCWq5fjypJcw9Y6ytyrkvkzby1Eg0E,235
|
53
|
-
hypern/hypern.pyi,sha256=
|
53
|
+
hypern/hypern.pyi,sha256=Urc3uHhPupsN-RRYluyWiw8B_xYWzE91Og3Zft40NgU,9010
|
54
54
|
hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
55
|
hypern/logging/logger.py,sha256=WACam_IJiCMXX0hGVKMGSxUQpY4DgAXy7M1dD3q-Z9s,3256
|
56
56
|
hypern/logging/__init__.py,sha256=6eVriyncsJ4J73fGYhoejv9MX7aGTkRezTpPxO4DX1I,52
|
@@ -78,13 +78,12 @@ hypern/routing/route.py,sha256=kan47-UeL-OPwcpp0rEhmBaaum6hN7FUj13Y8pZDEYA,10256
|
|
78
78
|
hypern/routing/__init__.py,sha256=U4xW5fDRsn03z4cVLT4dJHHGGU6SVxyv2DL86LXodeE,162
|
79
79
|
hypern/scheduler.py,sha256=-k3tW2AGCnHYSthKXk-FOs_SCtWp3yIxQzwzUJMJsbo,67
|
80
80
|
hypern/security.py,sha256=3E86Yp_eOSVa1emUvBrDgoF0Sn6eNX0CfLnt87w5CPI,1773
|
81
|
-
hypern/worker.py,sha256=
|
81
|
+
hypern/worker.py,sha256=ksJW8jWQg3HbIYnIZ5qdAmO-yh5hLpwvTT3dKkHR4Eo,9761
|
82
82
|
hypern/ws/channel.py,sha256=0ns2qmeoFJOpGLXS_hqldhywDQm_DxHwj6KloQx4Q3I,3183
|
83
83
|
hypern/ws/heartbeat.py,sha256=sWMXzQm6cbDHHA2NHc-gFjv7G_E56XtxswHQ93_BueM,2861
|
84
84
|
hypern/ws/room.py,sha256=0_L6Nun0n007F0rfNY8yX5x_A8EuXuI67JqpMkJ4RNI,2598
|
85
85
|
hypern/ws/route.py,sha256=fGQ2RC708MPOiiIHPUo8aZ-oK379TTAyQYm4htNA5jM,803
|
86
86
|
hypern/ws/__init__.py,sha256=dhRoRY683_rfPfSPM5qUczfTuyYDeuLOCFxY4hIdKt8,131
|
87
|
-
hypern/
|
88
|
-
hypern/
|
89
|
-
hypern
|
90
|
-
hypern-0.3.7.dist-info/RECORD,,
|
87
|
+
hypern/__init__.py,sha256=p3AtJQbsPs1RYEiN1thxH-k5UP8FfLeiJSoP0Vt3MDg,639
|
88
|
+
hypern/hypern.cp312-win32.pyd,sha256=rvh5yYWRidA1p4JK6cneYi7yjaJ8okvTNHUIpwn6c9k,7591936
|
89
|
+
hypern-0.3.8.dist-info/RECORD,,
|
hypern/ws.py
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
from .hypern import WebsocketRoute as WebsocketRouteInternal, WebSocketSession
|
2
|
-
|
3
|
-
|
4
|
-
class WebsocketRoute:
|
5
|
-
def __init__(self) -> None:
|
6
|
-
self.routes = []
|
7
|
-
|
8
|
-
def on(self, path):
|
9
|
-
def wrapper(func):
|
10
|
-
self.routes.append(WebsocketRouteInternal(path, func))
|
11
|
-
return func
|
12
|
-
|
13
|
-
return wrapper
|
14
|
-
|
15
|
-
|
16
|
-
__all__ = ["WebsocketRoute", "WebSocketSession"]
|
File without changes
|