hypern 0.2.0__cp312-cp312-macosx_10_12_x86_64.whl → 0.3.0__cp312-cp312-macosx_10_12_x86_64.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/application.py +35 -42
- hypern/args_parser.py +59 -0
- hypern/hypern.cpython-312-darwin.so +0 -0
- hypern/hypern.pyi +32 -3
- hypern/logging/logger.py +1 -1
- hypern/middleware/limit.py +1 -1
- hypern/processpool.py +37 -6
- hypern/reload.py +60 -0
- hypern/routing/route.py +1 -1
- hypern/ws.py +16 -0
- {hypern-0.2.0.dist-info → hypern-0.3.0.dist-info}/METADATA +3 -2
- {hypern-0.2.0.dist-info → hypern-0.3.0.dist-info}/RECORD +14 -11
- {hypern-0.2.0.dist-info → hypern-0.3.0.dist-info}/WHEEL +1 -1
- {hypern-0.2.0.dist-info → hypern-0.3.0.dist-info}/licenses/LICENSE +0 -0
hypern/application.py
CHANGED
|
@@ -2,23 +2,21 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
|
-
import socket
|
|
6
5
|
from typing import Any, Callable, List, TypeVar
|
|
7
6
|
|
|
8
7
|
import orjson
|
|
9
8
|
from typing_extensions import Annotated, Doc
|
|
10
9
|
|
|
11
10
|
from hypern.datastructures import Contact, HTTPMethod, Info, License
|
|
12
|
-
from hypern.
|
|
13
|
-
from hypern.hypern import FunctionInfo, Router
|
|
14
|
-
from hypern.hypern import Route as InternalRoute
|
|
15
|
-
from hypern.logging import logger
|
|
11
|
+
from hypern.hypern import FunctionInfo, Router, Route as InternalRoute, WebsocketRouter
|
|
16
12
|
from hypern.openapi import SchemaGenerator, SwaggerUI
|
|
17
13
|
from hypern.processpool import run_processes
|
|
18
14
|
from hypern.response import HTMLResponse, JSONResponse
|
|
19
15
|
from hypern.routing import Route
|
|
20
16
|
from hypern.scheduler import Scheduler
|
|
21
17
|
from hypern.middleware import Middleware
|
|
18
|
+
from hypern.args_parser import ArgsConfig
|
|
19
|
+
from hypern.ws import WebsocketRoute
|
|
22
20
|
|
|
23
21
|
AppType = TypeVar("AppType", bound="Hypern")
|
|
24
22
|
|
|
@@ -49,6 +47,15 @@ class Hypern:
|
|
|
49
47
|
"""
|
|
50
48
|
),
|
|
51
49
|
] = None,
|
|
50
|
+
websockets: Annotated[
|
|
51
|
+
List[WebsocketRoute] | None,
|
|
52
|
+
Doc(
|
|
53
|
+
"""
|
|
54
|
+
A list of routes to serve incoming WebSocket requests.
|
|
55
|
+
You can define routes using the `WebsocketRoute` class from `Hypern
|
|
56
|
+
"""
|
|
57
|
+
),
|
|
58
|
+
] = None,
|
|
52
59
|
title: Annotated[
|
|
53
60
|
str,
|
|
54
61
|
Doc(
|
|
@@ -188,15 +195,20 @@ class Hypern:
|
|
|
188
195
|
) -> None:
|
|
189
196
|
super().__init__(*args, **kwargs)
|
|
190
197
|
self.router = Router(path="/")
|
|
198
|
+
self.websocket_router = WebsocketRouter(path="/")
|
|
191
199
|
self.scheduler = scheduler
|
|
192
200
|
self.injectables = default_injectables or {}
|
|
193
201
|
self.middleware_before_request = []
|
|
194
202
|
self.middleware_after_request = []
|
|
195
203
|
self.response_headers = {}
|
|
204
|
+
self.args = ArgsConfig()
|
|
196
205
|
|
|
197
|
-
for route in routes:
|
|
206
|
+
for route in routes or []:
|
|
198
207
|
self.router.extend_route(route(app=self).routes)
|
|
199
208
|
|
|
209
|
+
for websocket_route in websockets or []:
|
|
210
|
+
self.websocket_router.add_route(websocket_route)
|
|
211
|
+
|
|
200
212
|
if openapi_url and docs_url:
|
|
201
213
|
self.__add_openapi(
|
|
202
214
|
info=Info(
|
|
@@ -340,60 +352,31 @@ class Hypern:
|
|
|
340
352
|
self.after_request()(after_request)
|
|
341
353
|
return self
|
|
342
354
|
|
|
343
|
-
def is_port_in_use(self, port: int) -> bool:
|
|
344
|
-
try:
|
|
345
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
346
|
-
return s.connect_ex(("localhost", port)) == 0
|
|
347
|
-
except Exception:
|
|
348
|
-
raise InvalidPortNumber(f"Invalid port number: {port}")
|
|
349
|
-
|
|
350
355
|
def start(
|
|
351
356
|
self,
|
|
352
|
-
host: Annotated[str, Doc("The host to run the server on. Defaults to `127.0.0.1`")] = "127.0.0.1",
|
|
353
|
-
port: Annotated[int, Doc("The port to run the server on. Defaults to `8080`")] = 8080,
|
|
354
|
-
workers: Annotated[int, Doc("The number of workers to run. Defaults to `1`")] = 1,
|
|
355
|
-
processes: Annotated[int, Doc("The number of processes to run. Defaults to `1`")] = 1,
|
|
356
|
-
max_blocking_threads: Annotated[int, Doc("The maximum number of blocking threads. Defaults to `100`")] = 1,
|
|
357
|
-
check_port: Annotated[bool, Doc("Check if the port is already in use. Defaults to `True`")] = False,
|
|
358
357
|
):
|
|
359
358
|
"""
|
|
360
359
|
Starts the server with the specified configuration.
|
|
361
|
-
|
|
362
|
-
Args:
|
|
363
|
-
host (str): The host to run the server on. Defaults to `127.0.0.1`.
|
|
364
|
-
port (int): The port to run the server on. Defaults to `8080`.
|
|
365
|
-
workers (int): The number of workers to run. Defaults to `1`.
|
|
366
|
-
processes (int): The number of processes to run. Defaults to `1`.
|
|
367
|
-
max_blocking_threads (int): The maximum number of blocking threads. Defaults to `100`.
|
|
368
|
-
check_port (bool): Check if the port is already in use. Defaults to `True`.
|
|
369
|
-
|
|
370
360
|
Raises:
|
|
371
361
|
ValueError: If an invalid port number is entered when prompted.
|
|
372
362
|
|
|
373
363
|
"""
|
|
374
|
-
if check_port:
|
|
375
|
-
while self.is_port_in_use(port):
|
|
376
|
-
logger.error("Port %s is already in use. Please use a different port.", port)
|
|
377
|
-
try:
|
|
378
|
-
port = int(input("Enter a different port: "))
|
|
379
|
-
except Exception:
|
|
380
|
-
logger.error("Invalid port number. Please enter a valid port number.")
|
|
381
|
-
continue
|
|
382
|
-
|
|
383
364
|
if self.scheduler:
|
|
384
365
|
self.scheduler.start()
|
|
385
366
|
|
|
386
367
|
run_processes(
|
|
387
|
-
host=host,
|
|
388
|
-
port=port,
|
|
389
|
-
workers=workers,
|
|
390
|
-
processes=processes,
|
|
391
|
-
max_blocking_threads=max_blocking_threads,
|
|
368
|
+
host=self.args.host,
|
|
369
|
+
port=self.args.port,
|
|
370
|
+
workers=self.args.workers,
|
|
371
|
+
processes=self.args.processes,
|
|
372
|
+
max_blocking_threads=self.args.max_blocking_threads,
|
|
392
373
|
router=self.router,
|
|
374
|
+
websocket_router=self.websocket_router,
|
|
393
375
|
injectables=self.injectables,
|
|
394
376
|
before_request=self.middleware_before_request,
|
|
395
377
|
after_request=self.middleware_after_request,
|
|
396
378
|
response_headers=self.response_headers,
|
|
379
|
+
reload=self.args.reload,
|
|
397
380
|
)
|
|
398
381
|
|
|
399
382
|
def add_route(self, method: HTTPMethod, endpoint: str, handler: Callable[..., Any]):
|
|
@@ -410,3 +393,13 @@ class Hypern:
|
|
|
410
393
|
func_info = FunctionInfo(handler=handler, is_async=is_async)
|
|
411
394
|
route = InternalRoute(path=endpoint, function=func_info, method=method.name)
|
|
412
395
|
self.router.add_route(route=route)
|
|
396
|
+
|
|
397
|
+
def add_websocket(self, ws_route: WebsocketRoute):
|
|
398
|
+
"""
|
|
399
|
+
Adds a WebSocket route to the WebSocket router.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
ws_route (WebsocketRoute): The WebSocket route to be added to the router.
|
|
403
|
+
"""
|
|
404
|
+
for route in ws_route.routes:
|
|
405
|
+
self.websocket_router.add_route(route=route)
|
hypern/args_parser.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
args, _ = parser.parse_known_args()
|
|
53
|
+
|
|
54
|
+
self.host = args.host or "127.0.0.1"
|
|
55
|
+
self.port = args.port or 5000
|
|
56
|
+
self.max_blocking_threads = args.max_blocking_threads or 100
|
|
57
|
+
self.processes = args.processes or 1
|
|
58
|
+
self.workers = args.workers or 1
|
|
59
|
+
self.reload = args.reload or False
|
|
Binary file
|
hypern/hypern.pyi
CHANGED
|
@@ -183,9 +183,6 @@ class FunctionInfo:
|
|
|
183
183
|
handler: Callable
|
|
184
184
|
is_async: bool
|
|
185
185
|
|
|
186
|
-
class SocketHeld:
|
|
187
|
-
socket: Any
|
|
188
|
-
|
|
189
186
|
@dataclass
|
|
190
187
|
class Server:
|
|
191
188
|
router: Router
|
|
@@ -195,6 +192,7 @@ class Server:
|
|
|
195
192
|
|
|
196
193
|
def add_route(self, route: Route) -> None: ...
|
|
197
194
|
def set_router(self, router: Router) -> None: ...
|
|
195
|
+
def set_websocket_router(self, websocket_router: WebsocketRouter) -> None: ...
|
|
198
196
|
def start(self, socket: SocketHeld, worker: int, max_blocking_threads: int) -> None: ...
|
|
199
197
|
def inject(self, key: str, value: Any) -> None: ...
|
|
200
198
|
def set_injected(self, injected: Dict[str, Any]) -> None: ...
|
|
@@ -227,6 +225,34 @@ class Router:
|
|
|
227
225
|
def get_routes_by_method(self, method: str) -> List[Route]: ...
|
|
228
226
|
def extend_route(self, routes: List[Route]) -> None: ...
|
|
229
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
|
+
|
|
230
256
|
@dataclass
|
|
231
257
|
class Header:
|
|
232
258
|
headers: Dict[str, str]
|
|
@@ -264,3 +290,6 @@ class Request:
|
|
|
264
290
|
path_params: Dict[str, str]
|
|
265
291
|
body: BodyData
|
|
266
292
|
method: str
|
|
293
|
+
remote_addr: str
|
|
294
|
+
timestamp: float
|
|
295
|
+
context_id: str
|
hypern/logging/logger.py
CHANGED
|
@@ -59,7 +59,7 @@ class ColourizedFormatter(logging.Formatter):
|
|
|
59
59
|
recordcopy.__dict__["message"] = recordcopy.getMessage()
|
|
60
60
|
recordcopy.__dict__["levelprefix"] = levelname + separator
|
|
61
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-%
|
|
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
63
|
recordcopy.__dict__["filename"] = click.style(f"{module}/{filename}:{lineno}:", fg=(101, 111, 104))
|
|
64
64
|
return super().formatMessage(recordcopy)
|
|
65
65
|
|
hypern/middleware/limit.py
CHANGED
hypern/processpool.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import os
|
|
2
3
|
import signal
|
|
3
4
|
import sys
|
|
4
5
|
from typing import Any, Dict, List
|
|
5
6
|
|
|
6
7
|
from multiprocess import Process
|
|
8
|
+
from watchdog.observers import Observer
|
|
7
9
|
|
|
8
|
-
from .hypern import FunctionInfo, Router, Server, SocketHeld
|
|
10
|
+
from .hypern import FunctionInfo, Router, Server, SocketHeld, WebsocketRouter
|
|
9
11
|
from .logging import logger
|
|
12
|
+
from .reload import EventHandler
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
def run_processes(
|
|
@@ -16,14 +19,18 @@ def run_processes(
|
|
|
16
19
|
processes: int,
|
|
17
20
|
max_blocking_threads: int,
|
|
18
21
|
router: Router,
|
|
22
|
+
websocket_router: WebsocketRouter,
|
|
19
23
|
injectables: Dict[str, Any],
|
|
20
24
|
before_request: List[FunctionInfo],
|
|
21
25
|
after_request: List[FunctionInfo],
|
|
22
26
|
response_headers: Dict[str, str],
|
|
27
|
+
reload: bool = True,
|
|
23
28
|
) -> List[Process]:
|
|
24
29
|
socket = SocketHeld(host, port)
|
|
25
30
|
|
|
26
|
-
process_pool = init_processpool(
|
|
31
|
+
process_pool = init_processpool(
|
|
32
|
+
router, websocket_router, socket, workers, processes, max_blocking_threads, injectables, before_request, after_request, response_headers
|
|
33
|
+
)
|
|
27
34
|
|
|
28
35
|
def terminating_signal_handler(_sig, _frame):
|
|
29
36
|
logger.info("Terminating server!!")
|
|
@@ -33,15 +40,37 @@ def run_processes(
|
|
|
33
40
|
signal.signal(signal.SIGINT, terminating_signal_handler)
|
|
34
41
|
signal.signal(signal.SIGTERM, terminating_signal_handler)
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
if reload:
|
|
44
|
+
# Set up file system watcher for auto-reload
|
|
45
|
+
watch_dirs = [os.getcwd()]
|
|
46
|
+
observer = Observer()
|
|
47
|
+
reload_handler = EventHandler(file_path=sys.argv[0], directory_path=os.getcwd())
|
|
48
|
+
|
|
49
|
+
for directory in watch_dirs:
|
|
50
|
+
observer.schedule(reload_handler, directory, recursive=True)
|
|
51
|
+
|
|
52
|
+
observer.start()
|
|
53
|
+
|
|
54
|
+
logger.info(f"Server started at http://{host}:{port}")
|
|
55
|
+
logger.info("Press Ctrl + C to stop")
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
for process in process_pool:
|
|
59
|
+
logger.debug(f"Process {process.pid} started")
|
|
60
|
+
process.join()
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
pass
|
|
63
|
+
finally:
|
|
64
|
+
if reload:
|
|
65
|
+
observer.stop()
|
|
66
|
+
observer.join()
|
|
39
67
|
|
|
40
68
|
return process_pool
|
|
41
69
|
|
|
42
70
|
|
|
43
71
|
def init_processpool(
|
|
44
72
|
router: Router,
|
|
73
|
+
websocket_router: WebsocketRouter,
|
|
45
74
|
socket: SocketHeld,
|
|
46
75
|
workers: int,
|
|
47
76
|
processes: int,
|
|
@@ -57,7 +86,7 @@ def init_processpool(
|
|
|
57
86
|
copied_socket = socket.try_clone()
|
|
58
87
|
process = Process(
|
|
59
88
|
target=spawn_process,
|
|
60
|
-
args=(router, copied_socket, workers, max_blocking_threads, injectables, before_request, after_request, response_headers),
|
|
89
|
+
args=(router, websocket_router, copied_socket, workers, max_blocking_threads, injectables, before_request, after_request, response_headers),
|
|
61
90
|
)
|
|
62
91
|
process.start()
|
|
63
92
|
process_pool.append(process)
|
|
@@ -81,6 +110,7 @@ def initialize_event_loop():
|
|
|
81
110
|
|
|
82
111
|
def spawn_process(
|
|
83
112
|
router: Router,
|
|
113
|
+
websocket_router: WebsocketRouter,
|
|
84
114
|
socket: SocketHeld,
|
|
85
115
|
workers: int,
|
|
86
116
|
max_blocking_threads: int,
|
|
@@ -93,6 +123,7 @@ def spawn_process(
|
|
|
93
123
|
|
|
94
124
|
server = Server()
|
|
95
125
|
server.set_router(router=router)
|
|
126
|
+
server.set_websocket_router(websocket_router=websocket_router)
|
|
96
127
|
server.set_injected(injected=injectables)
|
|
97
128
|
server.set_before_hooks(hooks=before_request)
|
|
98
129
|
server.set_after_hooks(hooks=after_request)
|
hypern/reload.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
import subprocess
|
|
4
|
+
from watchdog.events import FileSystemEventHandler
|
|
5
|
+
|
|
6
|
+
from .logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EventHandler(FileSystemEventHandler):
|
|
10
|
+
def __init__(self, file_path: str, directory_path: str) -> None:
|
|
11
|
+
self.file_path = file_path
|
|
12
|
+
self.directory_path = directory_path
|
|
13
|
+
self.process = None # Keep track of the subprocess
|
|
14
|
+
self.last_reload = time.time() # Keep track of the last reload. EventHandler is initialized with the process.
|
|
15
|
+
|
|
16
|
+
def stop_server(self):
|
|
17
|
+
if self.process:
|
|
18
|
+
try:
|
|
19
|
+
# Check if the process is still alive
|
|
20
|
+
if self.process.poll() is None: # None means the process is still running
|
|
21
|
+
self.process.terminate() # Gracefully terminate the process
|
|
22
|
+
self.process.wait(timeout=5) # Wait for the process to exit
|
|
23
|
+
else:
|
|
24
|
+
logger.error("Process is not running.")
|
|
25
|
+
except subprocess.TimeoutExpired:
|
|
26
|
+
logger.error("Process did not terminate in time. Forcing termination.")
|
|
27
|
+
self.process.kill() # Forcefully kill the process if it doesn't stop
|
|
28
|
+
except ProcessLookupError:
|
|
29
|
+
logger.error("Process does not exist.")
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.error(f"An error occurred while stopping the server: {e}")
|
|
32
|
+
else:
|
|
33
|
+
logger.debug("No process to stop.")
|
|
34
|
+
|
|
35
|
+
def reload(self):
|
|
36
|
+
self.stop_server()
|
|
37
|
+
logger.debug("Reloading the server")
|
|
38
|
+
prev_process = self.process
|
|
39
|
+
if prev_process:
|
|
40
|
+
prev_process.kill()
|
|
41
|
+
|
|
42
|
+
self.process = subprocess.Popen(
|
|
43
|
+
[sys.executable, *sys.argv],
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.last_reload = time.time()
|
|
47
|
+
|
|
48
|
+
def on_modified(self, event) -> None:
|
|
49
|
+
"""
|
|
50
|
+
This function is a callback that will start a new server on every even change
|
|
51
|
+
|
|
52
|
+
:param event FSEvent: a data structure with info about the events
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Avoid reloading multiple times when watchdog detects multiple events
|
|
56
|
+
if time.time() - self.last_reload < 0.5:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
time.sleep(0.2) # Wait for the file to be fully written
|
|
60
|
+
self.reload()
|
hypern/routing/route.py
CHANGED
|
@@ -188,7 +188,7 @@ class Route:
|
|
|
188
188
|
docs["responses"] = {
|
|
189
189
|
"200": {
|
|
190
190
|
"description": "Successful response",
|
|
191
|
-
"content": {"application/json": {"schema": response_type.
|
|
191
|
+
"content": {"application/json": {"schema": pydantic_to_swagger(response_type).get(response_type.__name__)}},
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
|
hypern/ws.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: hypern
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
@@ -22,6 +22,7 @@ Requires-Dist: orjson ==3.10.11
|
|
|
22
22
|
Requires-Dist: multiprocess ==0.70.17
|
|
23
23
|
Requires-Dist: uvloop ==0.21.0 ; sys_platform != 'win32' and platform_python_implementation == 'CPython' and platform_machine != 'armv7l'
|
|
24
24
|
Requires-Dist: cryptography ==43.0.3
|
|
25
|
+
Requires-Dist: watchdog ==6.0.0
|
|
25
26
|
License-File: LICENSE
|
|
26
27
|
Summary: A Fast Async Python backend with a Rust runtime.
|
|
27
28
|
Author-email: Martin Dang <vannghiem848@gmail.com>
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
hypern-0.
|
|
2
|
-
hypern-0.
|
|
3
|
-
hypern-0.
|
|
1
|
+
hypern-0.3.0.dist-info/METADATA,sha256=SINnZRpXUXjLnonzyUZ-STXzjY1JRGPl_nu0vqF0Qkg,3658
|
|
2
|
+
hypern-0.3.0.dist-info/WHEEL,sha256=VOE5xsTXcJeWHCzbtBjiQ_IBfNhbwWgDGyUAf1tvnl8,106
|
|
3
|
+
hypern-0.3.0.dist-info/licenses/LICENSE,sha256=VdbaK2hSaaD-LUjtDIlEbeZVmvLGK7BEQvltP3mv-cY,1304
|
|
4
4
|
hypern/worker.py,sha256=kN93QGx4bst0inHRO_fsTzMmhDA0N3pdzaqNXwyu3N0,894
|
|
5
5
|
hypern/middleware/cors.py,sha256=SfG-3vAS-4MPXqsIsegNwDx9mqC9lvgUc3RuYzN6HNg,1643
|
|
6
6
|
hypern/middleware/__init__.py,sha256=YpgxL7GQkzZM91VCNxHT2xmTa1R1b_BUS8n3tZ2b1Ys,268
|
|
7
|
-
hypern/middleware/limit.py,sha256=
|
|
7
|
+
hypern/middleware/limit.py,sha256=9EA79q2GgyZkRynMJj8rfgumEhJKbAvyi3jII6A_BX8,7976
|
|
8
8
|
hypern/middleware/i18n.py,sha256=s82nQo6kKClZ0s3G3jsy87VRfwxpBDbASB_ErjRL3O0,15
|
|
9
9
|
hypern/middleware/base.py,sha256=Llcg9wglcumvY4BqaTfrX1OOZDqns4wb34wGF55EXcI,523
|
|
10
10
|
hypern/config.py,sha256=v9KLL6snReAETKiVb8x3KOFrXpYd8-Io5tM7eruR85U,4781
|
|
11
11
|
hypern/response/__init__.py,sha256=9z99BDgASpG404GK8LGkOsXgac0wFwH_cQOTI5Ju-1U,223
|
|
12
12
|
hypern/response/response.py,sha256=s6KqscjA7jl8RaZh5gZQgVksPtHKzsRrQywDcEjVSR4,4448
|
|
13
|
+
hypern/reload.py,sha256=oRwDyxze69ERiVT41eJI_qpg6eJFDbIszGPVAoS5DHs,2233
|
|
13
14
|
hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
15
|
hypern/auth/authorization.py,sha256=b8N_MiBmSJBVR45SsOI2iMGmXYOlNKbsMsrMwXApxtk,30
|
|
15
16
|
hypern/security.py,sha256=dOWdNHA8SoUhlbu6Z2MapOwT9tAhlsierrTBBiEV5-A,1729
|
|
@@ -20,9 +21,10 @@ hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
20
21
|
hypern/openapi/swagger.py,sha256=E5fHYUfFa77zQsCyQGf_vnqJVpl4_KI5qsKFHdgJEdw,61
|
|
21
22
|
hypern/openapi/__init__.py,sha256=oJ0HM9yAgSN00mBC_fRgV2irlGugrhvIpiveuDMv8PM,136
|
|
22
23
|
hypern/openapi/schemas.py,sha256=nmcmNYvKmjNkwFqi_3qpXVi1ukanNxMVay68bOLTrx8,1624
|
|
23
|
-
hypern/
|
|
24
|
-
hypern/
|
|
25
|
-
hypern/
|
|
24
|
+
hypern/ws.py,sha256=XImBSYW2SM9s8wprnis8c2uRPhYxIXuDhwgRLLLDU38,378
|
|
25
|
+
hypern/application.py,sha256=yXrKhVKwCQDweHoZPFyWr0ws7nmCOPp6H3aLZLbD7sw,13923
|
|
26
|
+
hypern/hypern.pyi,sha256=WlE2tNIY3FnJaUorHKfJ7tY-DZf83XK7bRvzrxauyr4,7405
|
|
27
|
+
hypern/processpool.py,sha256=mtP6qYSKTUgHOQEqtw6yhBvZ4JMAPw6uLxuyd-96CYI,3908
|
|
26
28
|
hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
29
|
hypern/db/nosql/addons/color.py,sha256=8zUBFjR19J1zy_WaOCXlS14GjgdOaHAcuSb__zFp_II,430
|
|
28
30
|
hypern/db/nosql/addons/unicode.py,sha256=vKyqfzrS9SKFo4eKlvu6klST0FrjM0wTx5N3ocvSSO8,258
|
|
@@ -44,8 +46,9 @@ hypern/db/sql/repository.py,sha256=YShEl1DqRXtYwNnn1F3YVfXX-TEdoNRjDEWIO_3Lu2s,9
|
|
|
44
46
|
hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
47
|
hypern/exceptions.py,sha256=wpTSTzw32Sb6bY9YxCDM7W_-Ww6u6pB1GKNbFf-1oj0,2331
|
|
46
48
|
hypern/scheduler.py,sha256=nQoWIYMRmKd30g4XwdB072hWTNHvJlLd9S6rLlTFKS0,62
|
|
49
|
+
hypern/args_parser.py,sha256=kJQtzw2xZrumDBzLQaZyEejAT02rUetPYCrmOpjzxWY,1731
|
|
47
50
|
hypern/routing/dispatcher.py,sha256=aujogCVTz2mYtZRkEtmpdlxXA9l6X4D072qOiIg-a_Q,2301
|
|
48
|
-
hypern/routing/route.py,sha256=
|
|
51
|
+
hypern/routing/route.py,sha256=AZc4Qo5iy74q0_U8E5X6RIsudznHZYEZR8MdHRoCmB4,10119
|
|
49
52
|
hypern/routing/__init__.py,sha256=MtyPYRHYMWIiCReZsUjJH93PvluotCbPU3RnWFQQmrA,97
|
|
50
53
|
hypern/routing/parser.py,sha256=4BFn8MAmSX1QplwBXEEgbabYiNUAllYf2svPZoPPD5k,3454
|
|
51
54
|
hypern/routing/endpoint.py,sha256=AWLHLQNlSGR8IGU6xM0RP-1kP06OJQzqpbXKSiZEzEo,996
|
|
@@ -61,6 +64,6 @@ hypern/caching/base/__init__.py,sha256=8S6QSax1FVGzfve_fYIzPmhrpvxXG8WYoc7M9v25b
|
|
|
61
64
|
hypern/caching/base/key_maker.py,sha256=FPW9L-N8NSPPgRngqMUCOdLurm1qmqe-KTe4UctoBp8,224
|
|
62
65
|
hypern/caching/redis_backend.py,sha256=3FYzKCW0_OvoIMl-e9pARRGOUvRYGG7hGlaXEB18vnY,67
|
|
63
66
|
hypern/logging/__init__.py,sha256=lzYSz0382eIM3CvP0sZ6RbEEwYZwfeJEJh9cxQA6Rws,49
|
|
64
|
-
hypern/logging/logger.py,sha256=
|
|
65
|
-
hypern/hypern.cpython-312-darwin.so,sha256=
|
|
66
|
-
hypern-0.
|
|
67
|
+
hypern/logging/logger.py,sha256=62Qg4YAi_JDGV72Rd6R58jixqZk7anRqHbtnuBlkrwA,3174
|
|
68
|
+
hypern/hypern.cpython-312-darwin.so,sha256=sC62Ta4yt9PcWk4-2skGB3BuhdTNBHly4WUiTRNoV_I,6305232
|
|
69
|
+
hypern-0.3.0.dist-info/RECORD,,
|
|
File without changes
|