hypern 0.3.1__cp310-cp310-win_amd64.whl → 0.3.3__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. hypern/application.py +115 -16
  2. hypern/args_parser.py +34 -1
  3. hypern/caching/__init__.py +6 -0
  4. hypern/caching/backend.py +31 -0
  5. hypern/caching/redis_backend.py +200 -2
  6. hypern/caching/strategies.py +164 -71
  7. hypern/config.py +97 -0
  8. hypern/db/addons/__init__.py +5 -0
  9. hypern/db/addons/sqlalchemy/__init__.py +71 -0
  10. hypern/db/sql/__init__.py +13 -179
  11. hypern/db/sql/field.py +606 -0
  12. hypern/db/sql/model.py +116 -0
  13. hypern/db/sql/query.py +879 -0
  14. hypern/exceptions.py +10 -0
  15. hypern/gateway/__init__.py +6 -0
  16. hypern/gateway/aggregator.py +32 -0
  17. hypern/gateway/gateway.py +41 -0
  18. hypern/gateway/proxy.py +60 -0
  19. hypern/gateway/service.py +52 -0
  20. hypern/hypern.cp310-win_amd64.pyd +0 -0
  21. hypern/hypern.pyi +53 -18
  22. hypern/middleware/__init__.py +14 -2
  23. hypern/middleware/base.py +9 -14
  24. hypern/middleware/cache.py +177 -0
  25. hypern/middleware/compress.py +78 -0
  26. hypern/middleware/cors.py +6 -3
  27. hypern/middleware/limit.py +5 -4
  28. hypern/middleware/security.py +21 -16
  29. hypern/processpool.py +16 -32
  30. hypern/routing/__init__.py +2 -1
  31. hypern/routing/dispatcher.py +4 -0
  32. hypern/routing/queue.py +175 -0
  33. {hypern-0.3.1.dist-info → hypern-0.3.3.dist-info}/METADATA +3 -1
  34. hypern-0.3.3.dist-info/RECORD +84 -0
  35. hypern/caching/base/__init__.py +0 -8
  36. hypern/caching/base/backend.py +0 -3
  37. hypern/caching/base/key_maker.py +0 -8
  38. hypern/caching/cache_manager.py +0 -56
  39. hypern/caching/cache_tag.py +0 -10
  40. hypern/caching/custom_key_maker.py +0 -11
  41. hypern-0.3.1.dist-info/RECORD +0 -76
  42. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/__init__.py +0 -0
  43. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/color.py +0 -0
  44. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/daterange.py +0 -0
  45. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/datetime.py +0 -0
  46. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/encrypted.py +0 -0
  47. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/password.py +0 -0
  48. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/ts_vector.py +0 -0
  49. /hypern/db/{sql/addons → addons/sqlalchemy/fields}/unicode.py +0 -0
  50. /hypern/db/{sql → addons/sqlalchemy}/repository.py +0 -0
  51. {hypern-0.3.1.dist-info → hypern-0.3.3.dist-info}/WHEEL +0 -0
  52. {hypern-0.3.1.dist-info → hypern-0.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -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 Middleware, Request, Response
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: SecurityConfig):
48
- super().__init__()
49
- self.config = config
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.config.rate_limiting:
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.config.jwt_secret:
78
+ if not self.secur_config.jwt_secret:
78
79
  raise ValueError("JWT secret key is not configured")
79
80
 
80
- payload = {"user": user_data, "exp": datetime.utcnow() + timedelta(seconds=self.config.jwt_expires_in), "iat": datetime.utcnow()}
81
- return jwt.encode(payload, self.config.jwt_secret, algorithm=self.config.jwt_algorithm)
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.config.jwt_secret, algorithms=[self.config.jwt_algorithm])
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.config.cors_configuration:
129
+ if not self.secur_config.cors_configuration:
125
130
  return
126
131
 
127
- cors = self.config.cors_configuration
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.config.security_headers:
141
- response.headers.update(self.config.security_headers)
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.config.jwt_auth:
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.config.csrf_protection and request.method in ["POST", "PUT", "DELETE", "PATCH"]:
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
@@ -2,34 +2,33 @@ import asyncio
2
2
  import os
3
3
  import signal
4
4
  import sys
5
- from typing import Any, Dict, List
5
+ from typing import List
6
6
 
7
7
  from multiprocess import Process
8
8
  from watchdog.observers import Observer
9
9
 
10
- from .hypern import FunctionInfo, Router, Server, SocketHeld, WebsocketRouter
10
+ from .hypern import Server, SocketHeld
11
11
  from .logging import logger
12
12
  from .reload import EventHandler
13
13
 
14
14
 
15
15
  def run_processes(
16
+ server: Server,
16
17
  host: str,
17
18
  port: int,
18
19
  workers: int,
19
20
  processes: int,
20
21
  max_blocking_threads: int,
21
- router: Router,
22
- websocket_router: WebsocketRouter,
23
- injectables: Dict[str, Any],
24
- before_request: List[FunctionInfo],
25
- after_request: List[FunctionInfo],
26
- response_headers: Dict[str, str],
27
22
  reload: bool = True,
28
23
  ) -> List[Process]:
29
24
  socket = SocketHeld(host, port)
30
25
 
31
26
  process_pool = init_processpool(
32
- router, websocket_router, socket, workers, processes, max_blocking_threads, injectables, before_request, after_request, response_headers
27
+ server,
28
+ socket,
29
+ workers,
30
+ processes,
31
+ max_blocking_threads,
33
32
  )
34
33
 
35
34
  def terminating_signal_handler(_sig, _frame):
@@ -69,16 +68,11 @@ def run_processes(
69
68
 
70
69
 
71
70
  def init_processpool(
72
- router: Router,
73
- websocket_router: WebsocketRouter,
71
+ server: Server,
74
72
  socket: SocketHeld,
75
73
  workers: int,
76
74
  processes: int,
77
75
  max_blocking_threads: int,
78
- injectables: Dict[str, Any],
79
- before_request: List[FunctionInfo],
80
- after_request: List[FunctionInfo],
81
- response_headers: Dict[str, str],
82
76
  ) -> List[Process]:
83
77
  process_pool = []
84
78
 
@@ -86,7 +80,12 @@ def init_processpool(
86
80
  copied_socket = socket.try_clone()
87
81
  process = Process(
88
82
  target=spawn_process,
89
- args=(router, websocket_router, copied_socket, workers, max_blocking_threads, injectables, before_request, after_request, response_headers),
83
+ args=(
84
+ server,
85
+ copied_socket,
86
+ workers,
87
+ max_blocking_threads,
88
+ ),
90
89
  )
91
90
  process.start()
92
91
  process_pool.append(process)
@@ -109,29 +108,14 @@ def initialize_event_loop():
109
108
 
110
109
 
111
110
  def spawn_process(
112
- router: Router,
113
- websocket_router: WebsocketRouter,
111
+ server: Server,
114
112
  socket: SocketHeld,
115
113
  workers: int,
116
114
  max_blocking_threads: int,
117
- injectables: Dict[str, Any],
118
- before_request: List[FunctionInfo],
119
- after_request: List[FunctionInfo],
120
- response_headers: Dict[str, str],
121
115
  ):
122
116
  loop = initialize_event_loop()
123
117
 
124
- server = Server()
125
- server.set_router(router=router)
126
- server.set_websocket_router(websocket_router=websocket_router)
127
- server.set_injected(injected=injectables)
128
- server.set_before_hooks(hooks=before_request)
129
- server.set_after_hooks(hooks=after_request)
130
- server.set_response_headers(headers=response_headers)
131
-
132
118
  try:
133
119
  server.start(socket, workers, max_blocking_threads)
134
- loop = asyncio.get_event_loop()
135
- loop.run_forever()
136
120
  except KeyboardInterrupt:
137
121
  loop.close()
@@ -1,4 +1,5 @@
1
1
  from .route import Route
2
2
  from .endpoint import HTTPEndpoint
3
+ from .queue import QueuedHTTPEndpoint
3
4
 
4
- __all__ = ["Route", "HTTPEndpoint"]
5
+ __all__ = ["Route", "HTTPEndpoint", "QueuedHTTPEndpoint"]
@@ -15,6 +15,7 @@ from hypern.hypern import Request, Response
15
15
  from hypern.response import JSONResponse
16
16
 
17
17
  from .parser import InputHandler
18
+ from hypern.config import context_store
18
19
 
19
20
 
20
21
  def is_async_callable(obj: typing.Any) -> bool:
@@ -32,6 +33,9 @@ async def run_in_threadpool(func: typing.Callable, *args, **kwargs):
32
33
 
33
34
  async def dispatch(handler, request: Request, inject: typing.Dict[str, typing.Any]) -> Response:
34
35
  try:
36
+ # set context for global handler
37
+ context_store.set_context(request.context_id)
38
+
35
39
  is_async = is_async_callable(handler)
36
40
  signature = inspect.signature(handler)
37
41
  input_handler = InputHandler(request)
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypern
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -23,6 +23,8 @@ 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
25
  Requires-Dist: watchdog ==6.0.0
26
+ Requires-Dist: jsonschema ==4.23.0
27
+ Requires-Dist: psutil ==6.1.0
26
28
  License-File: LICENSE
27
29
  Summary: A Fast Async Python backend with a Rust runtime.
28
30
  Author-email: Martin Dang <vannghiem848@gmail.com>
@@ -0,0 +1,84 @@
1
+ hypern-0.3.3.dist-info/METADATA,sha256=VDUnlR9iin2qEAagUwnOdvZL1vC4dMeNM1oBrwl-rZ8,3819
2
+ hypern-0.3.3.dist-info/WHEEL,sha256=ieLtPNGsC7xoJjcmwA6ZxvTXpcxDlcXaGCM87uD_Bf0,96
3
+ hypern-0.3.3.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
4
+ hypern/application.py,sha256=TZKwSHpWSOaValZtogmMO18l8U_xOpFN7cARMFK1hns,18432
5
+ hypern/args_parser.py,sha256=zTfLfBoKBvYWxdPjabTfZsCtYF3La3PT0TD8dfLMeM4,2815
6
+ hypern/auth/authorization.py,sha256=-NprZsI0np889ZN1fp-MiVFrPoMNzUtatBJaCMtkllM,32
7
+ hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ hypern/background.py,sha256=xy38nQZSJsYFRXr3-uFJeNW9E1GiXXOC7lSe5pC0eyE,124
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
13
+ hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ hypern/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ hypern/config.py,sha256=Jij9eGg5NgC8Un5Lw5i7ghuEMAfkVctdcoE4RaN5LTE,8157
16
+ hypern/datastructures.py,sha256=zZGGSP07kPc9KJDf11hX5uYhAyRE-Ck5wezW5QtOVXw,897
17
+ hypern/db/addons/sqlalchemy/fields/color.py,sha256=Jj8q4lkT0ukKyjVyZjBx7fokGrX7AIJpKOGzwsBpfnU,480
18
+ hypern/db/addons/sqlalchemy/fields/daterange.py,sha256=qEfQN9c4jQdzeXNYKgQ4VIJ3Qc0HXHWRouzNF1se-RA,853
19
+ hypern/db/addons/sqlalchemy/fields/datetime.py,sha256=Bp2jMja2lb_b2WnzRnfbjXXTHBgBTyph1ECsItIwvvg,643
20
+ hypern/db/addons/sqlalchemy/fields/encrypted.py,sha256=pXsg4ImPpK-VkLDruKrv2gUtdp2kajQX7xDbRDxa-SI,1930
21
+ hypern/db/addons/sqlalchemy/fields/password.py,sha256=9pypORygWaINj3oiAOBRIOGgpuA0lcjPq4rh1pqJxq0,5789
22
+ hypern/db/addons/sqlalchemy/fields/ts_vector.py,sha256=bQyXYvQ1bAfFpYcS-sFwM7fU6L1lg9_7nGDRGp0CoUo,1361
23
+ hypern/db/addons/sqlalchemy/fields/unicode.py,sha256=rbqyHlsPUqRDWIjYQSRFF3zkHncnwxo0sdlzqUlcrUw,411
24
+ hypern/db/addons/sqlalchemy/fields/__init__.py,sha256=mLN_AvwgpSAbrWZvVHZuO7ff0gk1T_JbVwd5wug5nlw,359
25
+ hypern/db/addons/sqlalchemy/repository.py,sha256=ue6vWOTrnEPyDevlyh3v-7PU6GSfrZHYKrbXVuoS8UA,9516
26
+ hypern/db/addons/sqlalchemy/__init__.py,sha256=FuY78ubEwtifdQTVHhCrscYaAarlp2urgYBc_R77yt0,2766
27
+ hypern/db/addons/__init__.py,sha256=mdW0P0xvnK8htUk02ujvIaeHXl6w53JjrTS4ioNi1Bw,63
28
+ hypern/db/nosql/addons/color.py,sha256=bAGRuARCAYwZ1nO4jK0lzGYKmavTDtS34BxvrsetF74,446
29
+ hypern/db/nosql/addons/daterange.py,sha256=hGUSoVFqatNY-TB5wjZTq62iZpHpdsyRJIsHxsj1uDs,1192
30
+ hypern/db/nosql/addons/encrypted.py,sha256=B0M-uDqvZHVmIZcFdwcuC2MGsv0pGJFQ1lrOg8klR9U,1741
31
+ hypern/db/nosql/addons/password.py,sha256=jfZxvWFm6nV9EWpXq5Mj-jpqnl9QbokZj9WT14n7dKE,5035
32
+ hypern/db/nosql/addons/unicode.py,sha256=LaDpLfdoTcJuASPE-8fqOVD05H_uOx8gOdnyDn5Iu0c,268
33
+ hypern/db/nosql/addons/__init__.py,sha256=WEtPM8sPHilvga7zxwqvINeTkF0hdcfgPcAnHc4MASE,125
34
+ hypern/db/nosql/__init__.py,sha256=MH9YvlbRlbBCrQVNOdfTaK-hINwJxbJLmxwY9Mei7I8,644
35
+ hypern/db/sql/field.py,sha256=_5Et9pPmTI_bhUFN7PgdSiozip-Iv0eH2491J_WUvXU,20009
36
+ hypern/db/sql/model.py,sha256=5GtH3v6LfYIENlBN1NpIjZpsYFQyo36jX-b6jO1lNdU,3894
37
+ hypern/db/sql/query.py,sha256=_kBZbdQckYyawSIcuaUrE4C6eWgpeWyK1JVR3JWdh60,31964
38
+ hypern/db/sql/__init__.py,sha256=lCOGNTHaXNSJbuLLIOe2IWWNmX0MFQFPNCl2yytD2Xs,261
39
+ hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ hypern/enum.py,sha256=KcVziJj7vWvyie0r2rtxhrLzdtkZAsf0DY58oJ4tQl4,360
41
+ hypern/exceptions.py,sha256=rWQpWjnkLY51HBpKcWkFaVGCKoC_EiFUPrIBBfLh-eA,2608
42
+ hypern/gateway/aggregator.py,sha256=N1onAp9gdzpCR-E5VubkVoUjjEmVNxG8gDZx9rhnbXc,1132
43
+ hypern/gateway/gateway.py,sha256=26K2qvJUR-0JnN4IlhwvSSt7EYcpYrBVDuzZ1ivQQ34,1475
44
+ hypern/gateway/proxy.py,sha256=w1wcTplDnVrfjn7hb0M0yBVth5TGl88irF-MUYHysQQ,2463
45
+ hypern/gateway/service.py,sha256=PkRaM08olqM_j_4wRjEJCR8X8ZysAF2WOcfhWjaX2eo,1701
46
+ hypern/gateway/__init__.py,sha256=TpFWtqnJerW1-jCWq5fjypJcw9Y6ytyrkvkzby1Eg0E,235
47
+ hypern/hypern.pyi,sha256=4qHekIzH-I6CYch8PwYMus9Ou-JI901kgaLOGsnlOog,9389
48
+ hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
+ hypern/logging/logger.py,sha256=WACam_IJiCMXX0hGVKMGSxUQpY4DgAXy7M1dD3q-Z9s,3256
50
+ hypern/logging/__init__.py,sha256=6eVriyncsJ4J73fGYhoejv9MX7aGTkRezTpPxO4DX1I,52
51
+ hypern/middleware/base.py,sha256=3G7vxFXr0tDp28Bz3NeM76eo03qYGppm07N3yWoA_4U,369
52
+ hypern/middleware/cache.py,sha256=HFfghpnhS21DOsXLn6nt-TgRLCNNm6ZqTKPMgoXM0Mw,7583
53
+ hypern/middleware/compress.py,sha256=Zph3pQz15YrYB4dMUMbQnfWIFY8ovysgPMepbY_WV9k,3119
54
+ hypern/middleware/cors.py,sha256=pt5HyTd3J5L9Lvczo2xI8fxLmtntSbJq-CPU0vYXoAI,1800
55
+ hypern/middleware/i18n.py,sha256=jHzVzjTx1nnjbraZtIVOprrnSaeKMxZB8RuSqRp2I4s,16
56
+ hypern/middleware/limit.py,sha256=eAYARPjqxq8Ue0TCpnxlVRB5hv7hwBF0PxeD-bG6Sl0,8252
57
+ hypern/middleware/security.py,sha256=d9Qf2UNMN8wz-MLnG2wRb0Vgf55_IGZAje5hbc2T_HQ,7539
58
+ hypern/middleware/__init__.py,sha256=V-Gnv-Jf-14BVuA28z7PN7GBVQ9BBiBdab6-QnTPCfY,493
59
+ hypern/openapi/schemas.py,sha256=YHfMlPUeP5DzDX5ao3YH8p_25Vvyaf616dh6XDCUZRc,1677
60
+ hypern/openapi/swagger.py,sha256=naqUY3rFAEYA1ZLIlmDsMYaol0yIm6TVebdkFa5cMTc,64
61
+ hypern/openapi/__init__.py,sha256=4rEVD8pa0kdSpsy7ZkJ5JY0Z2XF0NGSKDMwYAd7YZpE,141
62
+ hypern/processpool.py,sha256=RFV4turo1dBv40NlzzosjwaZLC24igdyq6twY2IXCUE,2968
63
+ hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
+ hypern/reload.py,sha256=nfaZCoChrQetHNtIqN4Xzi-a0v-irxSCMhwCK3bCEq0,1569
65
+ hypern/response/response.py,sha256=-dnboAraPic8asf503PxwmDuxhNllUO5h97_DGmbER4,4582
66
+ hypern/response/__init__.py,sha256=_w3u3TDNuYx5ejnnN1unqnTY8NlBgUATQi6wepEB_FQ,226
67
+ hypern/routing/dispatcher.py,sha256=oQsbOTkjE5roFCl6k58oCW9lEGR_sY5tBoXSJIDgh0w,2508
68
+ hypern/routing/endpoint.py,sha256=RKVhvqOEGL9IKBXQ3KJgPi9bgJj9gfWC5BdZc5U_atc,1026
69
+ hypern/routing/parser.py,sha256=R-4lcN9Ha1iMeAjlqDe8HwkjjMVG-c-ubQLZyWKXj6M,3554
70
+ hypern/routing/queue.py,sha256=NtFBbogU22ddyyX-CuQMip1XFDPZdMCVMIeUCQ-CR6Y,7176
71
+ hypern/routing/route.py,sha256=IUnWU5ra-0R9rrRDpxJiwiw7vaEefn-We2dZ4EocJGw,10403
72
+ hypern/routing/__init__.py,sha256=U4xW5fDRsn03z4cVLT4dJHHGGU6SVxyv2DL86LXodeE,162
73
+ hypern/scheduler.py,sha256=-k3tW2AGCnHYSthKXk-FOs_SCtWp3yIxQzwzUJMJsbo,67
74
+ hypern/security.py,sha256=3E86Yp_eOSVa1emUvBrDgoF0Sn6eNX0CfLnt87w5CPI,1773
75
+ hypern/worker.py,sha256=WQrhY_awR6zjMwY4Q7izXi4E4fFrDqt7jIblUW8Bzcg,924
76
+ hypern/ws/channel.py,sha256=0ns2qmeoFJOpGLXS_hqldhywDQm_DxHwj6KloQx4Q3I,3183
77
+ hypern/ws/heartbeat.py,sha256=sWMXzQm6cbDHHA2NHc-gFjv7G_E56XtxswHQ93_BueM,2861
78
+ hypern/ws/room.py,sha256=0_L6Nun0n007F0rfNY8yX5x_A8EuXuI67JqpMkJ4RNI,2598
79
+ hypern/ws/route.py,sha256=fGQ2RC708MPOiiIHPUo8aZ-oK379TTAyQYm4htNA5jM,803
80
+ hypern/ws/__init__.py,sha256=dhRoRY683_rfPfSPM5qUczfTuyYDeuLOCFxY4hIdKt8,131
81
+ hypern/ws.py,sha256=F6SA2Z1KVnqTEX8ssvOXqCtudUS4eo30JsiIsvfbHnE,394
82
+ hypern/__init__.py,sha256=9Ww_aUQ0vJls0tOq7Yw1_TVOCRsa5bHJ-RtnSeComwk,119
83
+ hypern/hypern.cp310-win_amd64.pyd,sha256=B8_uOIWwg1dU_R5dQcJGSr9EFnRnIEyaiNCyuqEk8cA,11248640
84
+ hypern-0.3.3.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from .backend import BaseBackend
3
- from .key_maker import BaseKeyMaker
4
-
5
- __all__ = [
6
- "BaseKeyMaker",
7
- "BaseBackend",
8
- ]
@@ -1,3 +0,0 @@
1
- from hypern.hypern import BaseBackend
2
-
3
- __all__ = ["BaseBackend"]
@@ -1,8 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from abc import ABC, abstractmethod
3
- from typing import Callable
4
-
5
-
6
- class BaseKeyMaker(ABC):
7
- @abstractmethod
8
- async def make(self, function: Callable, prefix: str, identify_key: str) -> str: ...
@@ -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()
@@ -1,10 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from enum import Enum
3
-
4
-
5
- class CacheTag(Enum):
6
- GET_HEALTH_CHECK = "get_health_check"
7
- GET_USER_INFO = "get_user_info"
8
- GET_CATEGORIES = "get_categories"
9
- GET_HISTORY = "get_chat_history"
10
- GET_QUESTION = "get_question"
@@ -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)