hypern 0.3.1__cp312-cp312-win32.whl → 0.3.2__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/application.py +47 -8
- hypern/args_parser.py +7 -0
- hypern/caching/__init__.py +6 -0
- hypern/caching/backend.py +31 -0
- hypern/caching/redis_backend.py +200 -2
- hypern/caching/strategies.py +164 -71
- 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.cp312-win32.pyd +0 -0
- hypern/hypern.pyi +20 -18
- hypern/middleware/__init__.py +14 -2
- hypern/middleware/base.py +9 -14
- hypern/middleware/cache.py +177 -0
- hypern/middleware/compress.py +78 -0
- hypern/middleware/cors.py +6 -3
- hypern/middleware/limit.py +5 -4
- hypern/middleware/security.py +21 -16
- hypern/processpool.py +41 -2
- hypern/routing/__init__.py +2 -1
- hypern/routing/queue.py +175 -0
- {hypern-0.3.1.dist-info → hypern-0.3.2.dist-info}/METADATA +1 -1
- {hypern-0.3.1.dist-info → hypern-0.3.2.dist-info}/RECORD +27 -24
- hypern/caching/base/__init__.py +0 -8
- hypern/caching/base/backend.py +0 -3
- hypern/caching/base/key_maker.py +0 -8
- hypern/caching/cache_manager.py +0 -56
- hypern/caching/cache_tag.py +0 -10
- hypern/caching/custom_key_maker.py +0 -11
- {hypern-0.3.1.dist-info → hypern-0.3.2.dist-info}/WHEEL +0 -0
- {hypern-0.3.1.dist-info → hypern-0.3.2.dist-info}/licenses/LICENSE +0 -0
hypern/middleware/security.py
CHANGED
@@ -4,13 +4,14 @@ import secrets
|
|
4
4
|
import time
|
5
5
|
from base64 import b64decode, b64encode
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from datetime import datetime, timedelta
|
7
|
+
from datetime import datetime, timedelta, timezone
|
8
8
|
from typing import Any, Dict, List, Optional
|
9
9
|
|
10
10
|
import jwt
|
11
11
|
|
12
12
|
from hypern.exceptions import Forbidden, Unauthorized
|
13
|
-
from hypern.hypern import
|
13
|
+
from hypern.hypern import Request, Response
|
14
|
+
from .base import Middleware, MiddlewareConfig
|
14
15
|
|
15
16
|
|
16
17
|
@dataclass
|
@@ -44,16 +45,16 @@ class SecurityConfig:
|
|
44
45
|
|
45
46
|
|
46
47
|
class SecurityMiddleware(Middleware):
|
47
|
-
def __init__(self, config:
|
48
|
-
super().__init__()
|
49
|
-
self.
|
48
|
+
def __init__(self, secur_config: SecurityConfig, config: Optional[MiddlewareConfig] = None):
|
49
|
+
super().__init__(config)
|
50
|
+
self.secur_config = secur_config
|
50
51
|
self._secret_key = secrets.token_bytes(32)
|
51
52
|
self._token_lifetime = 3600
|
52
53
|
self._rate_limit_storage = {}
|
53
54
|
|
54
55
|
def _rate_limit_check(self, request: Request) -> Optional[Response]:
|
55
56
|
"""Check if the request exceeds rate limits"""
|
56
|
-
if not self.
|
57
|
+
if not self.secur_config.rate_limiting:
|
57
58
|
return None
|
58
59
|
|
59
60
|
client_ip = request.client.host
|
@@ -74,16 +75,20 @@ class SecurityMiddleware(Middleware):
|
|
74
75
|
|
75
76
|
def _generate_jwt_token(self, user_data: Dict[str, Any]) -> str:
|
76
77
|
"""Generate a JWT token"""
|
77
|
-
if not self.
|
78
|
+
if not self.secur_config.jwt_secret:
|
78
79
|
raise ValueError("JWT secret key is not configured")
|
79
80
|
|
80
|
-
payload = {
|
81
|
-
|
81
|
+
payload = {
|
82
|
+
"user": user_data,
|
83
|
+
"exp": datetime.now(tz=timezone.utc) + timedelta(seconds=self.secur_config.jwt_expires_in),
|
84
|
+
"iat": datetime.now(tz=timezone.utc),
|
85
|
+
}
|
86
|
+
return jwt.encode(payload, self.secur_config.jwt_secret, algorithm=self.secur_config.jwt_algorithm)
|
82
87
|
|
83
88
|
def _verify_jwt_token(self, token: str) -> Dict[str, Any]:
|
84
89
|
"""Verify JWT token and return payload"""
|
85
90
|
try:
|
86
|
-
payload = jwt.decode(token, self.
|
91
|
+
payload = jwt.decode(token, self.secur_config.jwt_secret, algorithms=[self.secur_config.jwt_algorithm])
|
87
92
|
return payload
|
88
93
|
except jwt.ExpiredSignatureError:
|
89
94
|
raise Unauthorized("Token has expired")
|
@@ -121,10 +126,10 @@ class SecurityMiddleware(Middleware):
|
|
121
126
|
|
122
127
|
def _apply_cors_headers(self, response: Response) -> None:
|
123
128
|
"""Apply CORS headers to response"""
|
124
|
-
if not self.
|
129
|
+
if not self.secur_config.cors_configuration:
|
125
130
|
return
|
126
131
|
|
127
|
-
cors = self.
|
132
|
+
cors = self.secur_config.cors_configuration
|
128
133
|
response.headers.update(
|
129
134
|
{
|
130
135
|
"Access-Control-Allow-Origin": ", ".join(cors.allowed_origins),
|
@@ -137,8 +142,8 @@ class SecurityMiddleware(Middleware):
|
|
137
142
|
|
138
143
|
def _apply_security_headers(self, response: Response) -> None:
|
139
144
|
"""Apply security headers to response"""
|
140
|
-
if self.
|
141
|
-
response.headers.update(self.
|
145
|
+
if self.secur_config.security_headers:
|
146
|
+
response.headers.update(self.secur_config.security_headers)
|
142
147
|
|
143
148
|
async def before_request(self, request: Request) -> Request | Response:
|
144
149
|
"""Process request before handling"""
|
@@ -147,7 +152,7 @@ class SecurityMiddleware(Middleware):
|
|
147
152
|
return rate_limit_response
|
148
153
|
|
149
154
|
# JWT authentication check
|
150
|
-
if self.
|
155
|
+
if self.secur_config.jwt_auth:
|
151
156
|
auth_header = request.headers.get("Authorization")
|
152
157
|
if not auth_header or not auth_header.startswith("Bearer "):
|
153
158
|
raise Unauthorized("Missing or invalid authorization header")
|
@@ -158,7 +163,7 @@ class SecurityMiddleware(Middleware):
|
|
158
163
|
return Response(status_code=401, description=str(e))
|
159
164
|
|
160
165
|
# CSRF protection check
|
161
|
-
if self.
|
166
|
+
if self.secur_config.csrf_protection and request.method in ["POST", "PUT", "DELETE", "PATCH"]:
|
162
167
|
csrf_token = request.headers.get("X-CSRF-Token")
|
163
168
|
if not csrf_token or not self._validate_csrf_token(csrf_token):
|
164
169
|
raise Forbidden("CSRF token missing or invalid")
|
hypern/processpool.py
CHANGED
@@ -25,11 +25,26 @@ def run_processes(
|
|
25
25
|
after_request: List[FunctionInfo],
|
26
26
|
response_headers: Dict[str, str],
|
27
27
|
reload: bool = True,
|
28
|
+
on_startup: FunctionInfo | None = None,
|
29
|
+
on_shutdown: FunctionInfo | None = None,
|
30
|
+
auto_compression: bool = False,
|
28
31
|
) -> List[Process]:
|
29
32
|
socket = SocketHeld(host, port)
|
30
33
|
|
31
34
|
process_pool = init_processpool(
|
32
|
-
router,
|
35
|
+
router,
|
36
|
+
websocket_router,
|
37
|
+
socket,
|
38
|
+
workers,
|
39
|
+
processes,
|
40
|
+
max_blocking_threads,
|
41
|
+
injectables,
|
42
|
+
before_request,
|
43
|
+
after_request,
|
44
|
+
response_headers,
|
45
|
+
on_startup,
|
46
|
+
on_shutdown,
|
47
|
+
auto_compression,
|
33
48
|
)
|
34
49
|
|
35
50
|
def terminating_signal_handler(_sig, _frame):
|
@@ -79,6 +94,9 @@ def init_processpool(
|
|
79
94
|
before_request: List[FunctionInfo],
|
80
95
|
after_request: List[FunctionInfo],
|
81
96
|
response_headers: Dict[str, str],
|
97
|
+
on_startup: FunctionInfo | None = None,
|
98
|
+
on_shutdown: FunctionInfo | None = None,
|
99
|
+
auto_compression: bool = False,
|
82
100
|
) -> List[Process]:
|
83
101
|
process_pool = []
|
84
102
|
|
@@ -86,7 +104,20 @@ def init_processpool(
|
|
86
104
|
copied_socket = socket.try_clone()
|
87
105
|
process = Process(
|
88
106
|
target=spawn_process,
|
89
|
-
args=(
|
107
|
+
args=(
|
108
|
+
router,
|
109
|
+
websocket_router,
|
110
|
+
copied_socket,
|
111
|
+
workers,
|
112
|
+
max_blocking_threads,
|
113
|
+
injectables,
|
114
|
+
before_request,
|
115
|
+
after_request,
|
116
|
+
response_headers,
|
117
|
+
on_startup,
|
118
|
+
on_shutdown,
|
119
|
+
auto_compression,
|
120
|
+
),
|
90
121
|
)
|
91
122
|
process.start()
|
92
123
|
process_pool.append(process)
|
@@ -118,6 +149,9 @@ def spawn_process(
|
|
118
149
|
before_request: List[FunctionInfo],
|
119
150
|
after_request: List[FunctionInfo],
|
120
151
|
response_headers: Dict[str, str],
|
152
|
+
on_startup: FunctionInfo | None = None,
|
153
|
+
on_shutdown: FunctionInfo | None = None,
|
154
|
+
auto_compression: bool = False,
|
121
155
|
):
|
122
156
|
loop = initialize_event_loop()
|
123
157
|
|
@@ -128,7 +162,12 @@ def spawn_process(
|
|
128
162
|
server.set_before_hooks(hooks=before_request)
|
129
163
|
server.set_after_hooks(hooks=after_request)
|
130
164
|
server.set_response_headers(headers=response_headers)
|
165
|
+
server.set_auto_compression(enabled=auto_compression)
|
131
166
|
|
167
|
+
if on_startup:
|
168
|
+
server.set_startup_handler(on_startup)
|
169
|
+
if on_shutdown:
|
170
|
+
server.set_shutdown_handler(on_shutdown)
|
132
171
|
try:
|
133
172
|
server.start(socket, workers, max_blocking_threads)
|
134
173
|
loop = asyncio.get_event_loop()
|
hypern/routing/__init__.py
CHANGED
hypern/routing/queue.py
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
import asyncio
|
2
|
+
import time
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from queue import PriorityQueue
|
6
|
+
from typing import Any, Dict
|
7
|
+
|
8
|
+
from hypern.hypern import Request, Response
|
9
|
+
from hypern.response import JSONResponse
|
10
|
+
from hypern.routing import HTTPEndpoint
|
11
|
+
from hypern.logging import logger
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass(order=True)
|
15
|
+
class PrioritizedRequest:
|
16
|
+
priority: int
|
17
|
+
timestamp: float = field(default_factory=time.time)
|
18
|
+
request: Request | None = field(default=None, compare=False)
|
19
|
+
future: asyncio.Future = field(compare=False, default_factory=asyncio.Future)
|
20
|
+
|
21
|
+
|
22
|
+
class QueuedHTTPEndpoint(HTTPEndpoint):
|
23
|
+
"""
|
24
|
+
HTTPEndpoint with request queuing capabilities for high-load scenarios.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, *args, **kwargs):
|
28
|
+
super().__init__(*args, **kwargs)
|
29
|
+
# Queue configuration
|
30
|
+
self._max_concurrent = kwargs.get("max_concurrent", 100)
|
31
|
+
self._queue_size = kwargs.get("queue_size", 1000)
|
32
|
+
self._request_timeout = kwargs.get("request_timeout", 30)
|
33
|
+
|
34
|
+
# Initialize queuing system
|
35
|
+
self._request_queue: PriorityQueue = PriorityQueue(maxsize=self._queue_size)
|
36
|
+
self._active_requests = 0
|
37
|
+
self._lock = None # Will be initialized when needed
|
38
|
+
self._request_semaphore = None # Will be initialized when needed
|
39
|
+
self._shutdown = False
|
40
|
+
self._queue_task = None
|
41
|
+
self._initialized = False
|
42
|
+
|
43
|
+
# Metrics
|
44
|
+
self._metrics = {"processed_requests": 0, "queued_requests": 0, "rejected_requests": 0, "avg_wait_time": 0.0}
|
45
|
+
|
46
|
+
self._fully_message = "Request queue is full"
|
47
|
+
|
48
|
+
async def _initialize(self):
|
49
|
+
"""Initialize async components when first request arrives"""
|
50
|
+
if not self._initialized:
|
51
|
+
self._lock = asyncio.Lock()
|
52
|
+
self._request_semaphore = asyncio.Semaphore(self._max_concurrent)
|
53
|
+
self._queue_task = asyncio.create_task(self._process_queue())
|
54
|
+
self._initialized = True
|
55
|
+
|
56
|
+
@asynccontextmanager
|
57
|
+
async def _queue_context(self, request: Request, priority: int = 10):
|
58
|
+
"""Context manager for handling request queuing."""
|
59
|
+
if self._shutdown:
|
60
|
+
raise RuntimeError("Endpoint is shutting down")
|
61
|
+
|
62
|
+
await self._initialize() # Ensure async components are initialized
|
63
|
+
|
64
|
+
request_future = asyncio.Future()
|
65
|
+
prioritized_request = PrioritizedRequest(priority=priority, timestamp=time.time(), request=request, future=request_future)
|
66
|
+
|
67
|
+
try:
|
68
|
+
if self._request_queue.qsize() >= self._queue_size:
|
69
|
+
self._metrics["rejected_requests"] += 1
|
70
|
+
raise asyncio.QueueFull(self._fully_message)
|
71
|
+
|
72
|
+
await self._enqueue_request(prioritized_request)
|
73
|
+
yield await asyncio.wait_for(request_future, timeout=self._request_timeout)
|
74
|
+
|
75
|
+
except asyncio.TimeoutError:
|
76
|
+
self._metrics["rejected_requests"] += 1
|
77
|
+
raise asyncio.TimeoutError("Request timed out while waiting in queue")
|
78
|
+
finally:
|
79
|
+
if not request_future.done():
|
80
|
+
request_future.cancel()
|
81
|
+
|
82
|
+
async def _enqueue_request(self, request: PrioritizedRequest):
|
83
|
+
"""Add request to the queue."""
|
84
|
+
try:
|
85
|
+
self._request_queue.put_nowait(request)
|
86
|
+
self._metrics["queued_requests"] += 1
|
87
|
+
except asyncio.QueueFull:
|
88
|
+
self._metrics["rejected_requests"] += 1
|
89
|
+
raise asyncio.QueueFull(self._fully_message)
|
90
|
+
|
91
|
+
async def _process_queue(self):
|
92
|
+
"""Background task to process queued requests."""
|
93
|
+
while not self._shutdown:
|
94
|
+
try:
|
95
|
+
if not self._request_queue.empty():
|
96
|
+
async with self._lock:
|
97
|
+
if self._active_requests >= self._max_concurrent:
|
98
|
+
await asyncio.sleep(0.1)
|
99
|
+
continue
|
100
|
+
|
101
|
+
request = self._request_queue.get_nowait()
|
102
|
+
wait_time = time.time() - request.timestamp
|
103
|
+
self._metrics["avg_wait_time"] = (self._metrics["avg_wait_time"] * self._metrics["processed_requests"] + wait_time) / (
|
104
|
+
self._metrics["processed_requests"] + 1
|
105
|
+
)
|
106
|
+
|
107
|
+
if not request.future.cancelled():
|
108
|
+
self._active_requests += 1
|
109
|
+
asyncio.create_task(self._handle_request(request))
|
110
|
+
|
111
|
+
await asyncio.sleep(0.01)
|
112
|
+
except Exception as e:
|
113
|
+
logger.error(f"Error processing queue: {e}")
|
114
|
+
await asyncio.sleep(1)
|
115
|
+
|
116
|
+
async def _handle_request(self, request: PrioritizedRequest):
|
117
|
+
"""Handle individual request."""
|
118
|
+
try:
|
119
|
+
async with self._request_semaphore:
|
120
|
+
response = await super().dispatch(request.request, {})
|
121
|
+
if not request.future.done():
|
122
|
+
request.future.set_result(response)
|
123
|
+
except Exception as e:
|
124
|
+
if not request.future.done():
|
125
|
+
request.future.set_exception(e)
|
126
|
+
finally:
|
127
|
+
self._active_requests -= 1
|
128
|
+
self._metrics["processed_requests"] += 1
|
129
|
+
self._metrics["queued_requests"] -= 1
|
130
|
+
|
131
|
+
async def dispatch(self, request: Request, inject: Dict[str, Any]) -> Response:
|
132
|
+
"""
|
133
|
+
Enhanced dispatch method with request queuing.
|
134
|
+
"""
|
135
|
+
try:
|
136
|
+
priority = self._get_request_priority(request)
|
137
|
+
|
138
|
+
async with self._queue_context(request, priority) as response:
|
139
|
+
return response
|
140
|
+
|
141
|
+
except asyncio.QueueFull:
|
142
|
+
return JSONResponse(description={"error": "Server too busy", "message": self._fully_message, "retry_after": 5}, status_code=503)
|
143
|
+
except asyncio.TimeoutError:
|
144
|
+
return JSONResponse(
|
145
|
+
description={
|
146
|
+
"error": "Request timeout",
|
147
|
+
"message": "Request timed out while waiting in queue",
|
148
|
+
},
|
149
|
+
status_code=504,
|
150
|
+
)
|
151
|
+
except Exception as e:
|
152
|
+
return JSONResponse(description={"error": "Internal server error", "message": str(e)}, status_code=500)
|
153
|
+
|
154
|
+
def _get_request_priority(self, request: Request) -> int:
|
155
|
+
"""
|
156
|
+
Determine request priority. Override this method to implement
|
157
|
+
custom priority logic.
|
158
|
+
"""
|
159
|
+
if request.method == "GET":
|
160
|
+
return 5
|
161
|
+
return 10
|
162
|
+
|
163
|
+
async def shutdown(self):
|
164
|
+
"""Gracefully shutdown the endpoint."""
|
165
|
+
self._shutdown = True
|
166
|
+
if self._queue_task and not self._queue_task.done():
|
167
|
+
self._queue_task.cancel()
|
168
|
+
try:
|
169
|
+
await self._queue_task
|
170
|
+
except asyncio.CancelledError:
|
171
|
+
pass
|
172
|
+
|
173
|
+
def get_metrics(self) -> Dict[str, Any]:
|
174
|
+
"""Get current queue metrics."""
|
175
|
+
return {**self._metrics, "current_queue_size": self._request_queue.qsize(), "active_requests": self._active_requests}
|
@@ -1,20 +1,15 @@
|
|
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.2.dist-info/METADATA,sha256=xbMB8V67yK1aLvRGbYrsjqoScFdSOZXs97zXg0yVVQ4,3754
|
2
|
+
hypern-0.3.2.dist-info/WHEEL,sha256=SK_cql1gpDHx6aBV-LOSvGbTt4TUC8AJJOzjOP2tdpI,92
|
3
|
+
hypern-0.3.2.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
|
4
|
+
hypern/application.py,sha256=yNlohWdSG0lpKfuTVX7N9gI3cmXl3IoEvP8WLH-yAiE,16251
|
5
|
+
hypern/args_parser.py,sha256=diz3Oq1PDNMBlG7ElqK01iokY5w_X3U1Ky1jikPVXRg,2020
|
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
|
9
|
-
hypern/caching/
|
10
|
-
hypern/caching/
|
11
|
-
hypern/caching/
|
12
|
-
hypern/caching/
|
13
|
-
hypern/caching/cache_tag.py,sha256=bZcjivMNETAzAHAIobuLN0S2wHgPgLLL8Gg4uso_qbk,267
|
14
|
-
hypern/caching/custom_key_maker.py,sha256=88RIIJjpQYFnv857wOlCKgWWBbK_S23zNHsIrJz_4PY,394
|
15
|
-
hypern/caching/redis_backend.py,sha256=IgQToCnHYGpKEErq2CNZkR5woo01z456Ef3C-XRPRV8,70
|
16
|
-
hypern/caching/strategies.py,sha256=S7qJiJOTJrk6KUFDrmjGkHP3XV8mYTRM3Sd4pA4UQKA,4199
|
17
|
-
hypern/caching/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
hypern/caching/backend.py,sha256=bfSEgJQxS3FDdj08CY4V9b9U3rwf4uy9gS46QmwA3Zc,829
|
10
|
+
hypern/caching/redis_backend.py,sha256=SWYlJvr8qi2kS_grhxhqQBLEEATpFremT2XyXkio8h0,5968
|
11
|
+
hypern/caching/strategies.py,sha256=qQjqgZLUX7KZjhwPD4SYUaMRewgCgsp6qa1FunK3Y0I,7288
|
12
|
+
hypern/caching/__init__.py,sha256=ODO7zMm4iFG8wcvrhKmukryG5wOTW0DnzFvNMfF57Cc,352
|
18
13
|
hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
14
|
hypern/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
15
|
hypern/config.py,sha256=frZSdXBI8GaM0tkw1Rs-XydZ9-XjGLRPj6DL4d51-y4,4930
|
@@ -39,20 +34,27 @@ hypern/db/sql/__init__.py,sha256=1UoWQi2CIcUAbQj3FadR-8V0o_b286nI2wYvOsvtbFc,647
|
|
39
34
|
hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
35
|
hypern/enum.py,sha256=KcVziJj7vWvyie0r2rtxhrLzdtkZAsf0DY58oJ4tQl4,360
|
41
36
|
hypern/exceptions.py,sha256=nHTkF0YdNBMKfSiNtjRMHMNKoY3RMUm68YYluuW15us,2428
|
42
|
-
hypern/
|
37
|
+
hypern/gateway/aggregator.py,sha256=N1onAp9gdzpCR-E5VubkVoUjjEmVNxG8gDZx9rhnbXc,1132
|
38
|
+
hypern/gateway/gateway.py,sha256=26K2qvJUR-0JnN4IlhwvSSt7EYcpYrBVDuzZ1ivQQ34,1475
|
39
|
+
hypern/gateway/proxy.py,sha256=w1wcTplDnVrfjn7hb0M0yBVth5TGl88irF-MUYHysQQ,2463
|
40
|
+
hypern/gateway/service.py,sha256=PkRaM08olqM_j_4wRjEJCR8X8ZysAF2WOcfhWjaX2eo,1701
|
41
|
+
hypern/gateway/__init__.py,sha256=TpFWtqnJerW1-jCWq5fjypJcw9Y6ytyrkvkzby1Eg0E,235
|
42
|
+
hypern/hypern.pyi,sha256=GiUjtY6tbV8d6KI1PDTTZK5zWyOD4hooahALnFd7Fl4,8197
|
43
43
|
hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
44
|
hypern/logging/logger.py,sha256=WACam_IJiCMXX0hGVKMGSxUQpY4DgAXy7M1dD3q-Z9s,3256
|
45
45
|
hypern/logging/__init__.py,sha256=6eVriyncsJ4J73fGYhoejv9MX7aGTkRezTpPxO4DX1I,52
|
46
|
-
hypern/middleware/base.py,sha256=
|
47
|
-
hypern/middleware/
|
46
|
+
hypern/middleware/base.py,sha256=3G7vxFXr0tDp28Bz3NeM76eo03qYGppm07N3yWoA_4U,369
|
47
|
+
hypern/middleware/cache.py,sha256=HFfghpnhS21DOsXLn6nt-TgRLCNNm6ZqTKPMgoXM0Mw,7583
|
48
|
+
hypern/middleware/compress.py,sha256=Zph3pQz15YrYB4dMUMbQnfWIFY8ovysgPMepbY_WV9k,3119
|
49
|
+
hypern/middleware/cors.py,sha256=pt5HyTd3J5L9Lvczo2xI8fxLmtntSbJq-CPU0vYXoAI,1800
|
48
50
|
hypern/middleware/i18n.py,sha256=jHzVzjTx1nnjbraZtIVOprrnSaeKMxZB8RuSqRp2I4s,16
|
49
|
-
hypern/middleware/limit.py,sha256=
|
50
|
-
hypern/middleware/security.py,sha256=
|
51
|
-
hypern/middleware/__init__.py,sha256=
|
51
|
+
hypern/middleware/limit.py,sha256=eAYARPjqxq8Ue0TCpnxlVRB5hv7hwBF0PxeD-bG6Sl0,8252
|
52
|
+
hypern/middleware/security.py,sha256=d9Qf2UNMN8wz-MLnG2wRb0Vgf55_IGZAje5hbc2T_HQ,7539
|
53
|
+
hypern/middleware/__init__.py,sha256=V-Gnv-Jf-14BVuA28z7PN7GBVQ9BBiBdab6-QnTPCfY,493
|
52
54
|
hypern/openapi/schemas.py,sha256=YHfMlPUeP5DzDX5ao3YH8p_25Vvyaf616dh6XDCUZRc,1677
|
53
55
|
hypern/openapi/swagger.py,sha256=naqUY3rFAEYA1ZLIlmDsMYaol0yIm6TVebdkFa5cMTc,64
|
54
56
|
hypern/openapi/__init__.py,sha256=4rEVD8pa0kdSpsy7ZkJ5JY0Z2XF0NGSKDMwYAd7YZpE,141
|
55
|
-
hypern/processpool.py,sha256=
|
57
|
+
hypern/processpool.py,sha256=l6_PKRMFQ4GIMPLTMOEbIwWDsxER0NIj_wdxVWqA7zA,5042
|
56
58
|
hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
59
|
hypern/reload.py,sha256=nfaZCoChrQetHNtIqN4Xzi-a0v-irxSCMhwCK3bCEq0,1569
|
58
60
|
hypern/response/response.py,sha256=-dnboAraPic8asf503PxwmDuxhNllUO5h97_DGmbER4,4582
|
@@ -60,8 +62,9 @@ hypern/response/__init__.py,sha256=_w3u3TDNuYx5ejnnN1unqnTY8NlBgUATQi6wepEB_FQ,2
|
|
60
62
|
hypern/routing/dispatcher.py,sha256=i2wLAAW1ZXgpi5K2heGXhTODnP1WdQzaR5WlUjs1o9c,2368
|
61
63
|
hypern/routing/endpoint.py,sha256=RKVhvqOEGL9IKBXQ3KJgPi9bgJj9gfWC5BdZc5U_atc,1026
|
62
64
|
hypern/routing/parser.py,sha256=R-4lcN9Ha1iMeAjlqDe8HwkjjMVG-c-ubQLZyWKXj6M,3554
|
65
|
+
hypern/routing/queue.py,sha256=NtFBbogU22ddyyX-CuQMip1XFDPZdMCVMIeUCQ-CR6Y,7176
|
63
66
|
hypern/routing/route.py,sha256=IUnWU5ra-0R9rrRDpxJiwiw7vaEefn-We2dZ4EocJGw,10403
|
64
|
-
hypern/routing/__init__.py,sha256=
|
67
|
+
hypern/routing/__init__.py,sha256=U4xW5fDRsn03z4cVLT4dJHHGGU6SVxyv2DL86LXodeE,162
|
65
68
|
hypern/scheduler.py,sha256=-k3tW2AGCnHYSthKXk-FOs_SCtWp3yIxQzwzUJMJsbo,67
|
66
69
|
hypern/security.py,sha256=3E86Yp_eOSVa1emUvBrDgoF0Sn6eNX0CfLnt87w5CPI,1773
|
67
70
|
hypern/worker.py,sha256=WQrhY_awR6zjMwY4Q7izXi4E4fFrDqt7jIblUW8Bzcg,924
|
@@ -72,5 +75,5 @@ hypern/ws/route.py,sha256=fGQ2RC708MPOiiIHPUo8aZ-oK379TTAyQYm4htNA5jM,803
|
|
72
75
|
hypern/ws/__init__.py,sha256=dhRoRY683_rfPfSPM5qUczfTuyYDeuLOCFxY4hIdKt8,131
|
73
76
|
hypern/ws.py,sha256=F6SA2Z1KVnqTEX8ssvOXqCtudUS4eo30JsiIsvfbHnE,394
|
74
77
|
hypern/__init__.py,sha256=9Ww_aUQ0vJls0tOq7Yw1_TVOCRsa5bHJ-RtnSeComwk,119
|
75
|
-
hypern/hypern.cp312-win32.pyd,sha256=
|
76
|
-
hypern-0.3.
|
78
|
+
hypern/hypern.cp312-win32.pyd,sha256=JxNOpTLC3EaBfc6d3TLY05BbTHHx5ciyj3wSbOKbWIM,6539264
|
79
|
+
hypern-0.3.2.dist-info/RECORD,,
|
hypern/caching/base/__init__.py
DELETED
hypern/caching/base/backend.py
DELETED
hypern/caching/base/key_maker.py
DELETED
hypern/caching/cache_manager.py
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from functools import wraps
|
3
|
-
from typing import Callable, Dict, Type
|
4
|
-
|
5
|
-
from .base import BaseBackend, BaseKeyMaker
|
6
|
-
from .cache_tag import CacheTag
|
7
|
-
import orjson
|
8
|
-
|
9
|
-
|
10
|
-
class CacheManager:
|
11
|
-
def __init__(self):
|
12
|
-
self.backend = None
|
13
|
-
self.key_maker = None
|
14
|
-
|
15
|
-
def init(self, backend: BaseBackend, key_maker: BaseKeyMaker) -> None:
|
16
|
-
self.backend = backend
|
17
|
-
self.key_maker = key_maker
|
18
|
-
|
19
|
-
def cached(self, tag: CacheTag, ttl: int = 60, identify: Dict = {}) -> Type[Callable]:
|
20
|
-
def _cached(function):
|
21
|
-
@wraps(function)
|
22
|
-
async def __cached(*args, **kwargs):
|
23
|
-
if not self.backend or not self.key_maker:
|
24
|
-
raise ValueError("Backend or KeyMaker not initialized")
|
25
|
-
|
26
|
-
_identify_key = []
|
27
|
-
for key, values in identify.items():
|
28
|
-
_obj = kwargs.get(key, None)
|
29
|
-
if not _obj:
|
30
|
-
raise ValueError(f"Caching: Identify key {key} not found in kwargs")
|
31
|
-
for attr in values:
|
32
|
-
_identify_key.append(f"{attr}={getattr(_obj, attr)}")
|
33
|
-
_identify_key = ":".join(_identify_key)
|
34
|
-
|
35
|
-
key = await self.key_maker.make(function=function, prefix=tag.value, identify_key=_identify_key)
|
36
|
-
|
37
|
-
cached_response = self.backend.get(key=key)
|
38
|
-
if cached_response:
|
39
|
-
return orjson.loads(cached_response)
|
40
|
-
|
41
|
-
response = await function(*args, **kwargs)
|
42
|
-
self.backend.set(response=orjson.dumps(response).decode("utf-8"), key=key, ttl=ttl)
|
43
|
-
return response
|
44
|
-
|
45
|
-
return __cached
|
46
|
-
|
47
|
-
return _cached # type: ignore
|
48
|
-
|
49
|
-
async def remove_by_tag(self, tag: CacheTag) -> None:
|
50
|
-
await self.backend.delete_startswith(value=tag.value)
|
51
|
-
|
52
|
-
async def remove_by_prefix(self, prefix: str) -> None:
|
53
|
-
await self.backend.delete_startswith(value=prefix)
|
54
|
-
|
55
|
-
|
56
|
-
Cache = CacheManager()
|
hypern/caching/cache_tag.py
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from typing import Callable
|
3
|
-
import inspect
|
4
|
-
|
5
|
-
from hypern.caching.base import BaseKeyMaker
|
6
|
-
|
7
|
-
|
8
|
-
class CustomKeyMaker(BaseKeyMaker):
|
9
|
-
async def make(self, function: Callable, prefix: str, identify_key: str = "") -> str:
|
10
|
-
path = f"{prefix}:{inspect.getmodule(function).__name__}.{function.__name__}:{identify_key}" # type: ignore
|
11
|
-
return str(path)
|
File without changes
|
File without changes
|