hypern 0.3.2__cp312-cp312-win32.whl → 0.3.4__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 +71 -13
- hypern/args_parser.py +27 -1
- hypern/config.py +97 -0
- hypern/db/addons/__init__.py +5 -0
- hypern/db/addons/sqlalchemy/__init__.py +71 -0
- hypern/db/sql/__init__.py +13 -179
- hypern/db/sql/field.py +607 -0
- hypern/db/sql/model.py +116 -0
- hypern/db/sql/query.py +904 -0
- hypern/exceptions.py +10 -0
- hypern/hypern.cp312-win32.pyd +0 -0
- hypern/hypern.pyi +32 -0
- hypern/processpool.py +7 -62
- hypern/routing/dispatcher.py +4 -0
- {hypern-0.3.2.dist-info → hypern-0.3.4.dist-info}/METADATA +3 -1
- {hypern-0.3.2.dist-info → hypern-0.3.4.dist-info}/RECORD +27 -22
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/__init__.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/color.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/daterange.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/datetime.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/encrypted.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/password.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/ts_vector.py +0 -0
- /hypern/db/{sql/addons → addons/sqlalchemy/fields}/unicode.py +0 -0
- /hypern/db/{sql → addons/sqlalchemy}/repository.py +0 -0
- {hypern-0.3.2.dist-info → hypern-0.3.4.dist-info}/WHEEL +0 -0
- {hypern-0.3.2.dist-info → hypern-0.3.4.dist-info}/licenses/LICENSE +0 -0
hypern/application.py
CHANGED
@@ -2,26 +2,52 @@
|
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
4
|
import asyncio
|
5
|
+
from dataclasses import dataclass
|
5
6
|
from typing import Any, Callable, List, TypeVar
|
6
7
|
|
7
8
|
import orjson
|
9
|
+
import psutil
|
8
10
|
from typing_extensions import Annotated, Doc
|
9
11
|
|
12
|
+
from hypern.args_parser import ArgsConfig
|
10
13
|
from hypern.datastructures import Contact, HTTPMethod, Info, License
|
11
|
-
from hypern.hypern import FunctionInfo, Router,
|
14
|
+
from hypern.hypern import DatabaseConfig, FunctionInfo, MiddlewareConfig, Router, Server, WebsocketRouter
|
15
|
+
from hypern.hypern import Route as InternalRoute
|
16
|
+
from hypern.logging import logger
|
17
|
+
from hypern.middleware import Middleware
|
12
18
|
from hypern.openapi import SchemaGenerator, SwaggerUI
|
13
19
|
from hypern.processpool import run_processes
|
14
20
|
from hypern.response import HTMLResponse, JSONResponse
|
15
21
|
from hypern.routing import Route
|
16
22
|
from hypern.scheduler import Scheduler
|
17
|
-
from hypern.middleware import Middleware
|
18
|
-
from hypern.args_parser import ArgsConfig
|
19
23
|
from hypern.ws import WebsocketRoute
|
20
|
-
from hypern.logging import logger
|
21
24
|
|
22
25
|
AppType = TypeVar("AppType", bound="Hypern")
|
23
26
|
|
24
27
|
|
28
|
+
@dataclass
|
29
|
+
class ThreadConfig:
|
30
|
+
workers: int
|
31
|
+
max_blocking_threads: int
|
32
|
+
|
33
|
+
|
34
|
+
class ThreadConfigurator:
|
35
|
+
def __init__(self):
|
36
|
+
self._cpu_count = psutil.cpu_count(logical=True)
|
37
|
+
self._memory_gb = psutil.virtual_memory().total / (1024**3)
|
38
|
+
|
39
|
+
def get_config(self, concurrent_requests: int = None) -> ThreadConfig:
|
40
|
+
"""Calculate optimal thread configuration based on system resources."""
|
41
|
+
workers = max(2, self._cpu_count)
|
42
|
+
|
43
|
+
if concurrent_requests:
|
44
|
+
max_blocking = min(max(32, concurrent_requests * 2), workers * 4, int(self._memory_gb * 8))
|
45
|
+
else:
|
46
|
+
max_blocking = min(workers * 4, int(self._memory_gb * 8), 256)
|
47
|
+
|
48
|
+
return ThreadConfig(workers=workers, max_blocking_threads=max_blocking)
|
49
|
+
|
50
|
+
|
25
51
|
class Hypern:
|
26
52
|
def __init__(
|
27
53
|
self: AppType,
|
@@ -199,6 +225,14 @@ class Hypern:
|
|
199
225
|
"""
|
200
226
|
),
|
201
227
|
] = False,
|
228
|
+
database_config: Annotated[
|
229
|
+
DatabaseConfig | None,
|
230
|
+
Doc(
|
231
|
+
"""
|
232
|
+
The database configuration for the application.
|
233
|
+
"""
|
234
|
+
),
|
235
|
+
] = None,
|
202
236
|
*args: Any,
|
203
237
|
**kwargs: Any,
|
204
238
|
) -> None:
|
@@ -214,6 +248,8 @@ class Hypern:
|
|
214
248
|
self.start_up_handler = None
|
215
249
|
self.shutdown_handler = None
|
216
250
|
self.auto_compression = auto_compression
|
251
|
+
self.database_config = database_config
|
252
|
+
self.thread_config = ThreadConfigurator().get_config()
|
217
253
|
|
218
254
|
for route in routes or []:
|
219
255
|
self.router.extend_route(route(app=self).routes)
|
@@ -369,6 +405,15 @@ class Hypern:
|
|
369
405
|
after_request = FunctionInfo(handler=after_request, is_async=is_async)
|
370
406
|
self.middleware_after_request.append((after_request, middleware.config))
|
371
407
|
|
408
|
+
def set_database_config(self, config: DatabaseConfig):
|
409
|
+
"""
|
410
|
+
Sets the database configuration for the application.
|
411
|
+
|
412
|
+
Args:
|
413
|
+
config (DatabaseConfig): The database configuration to be set.
|
414
|
+
"""
|
415
|
+
self.database_config = config
|
416
|
+
|
372
417
|
def start(
|
373
418
|
self,
|
374
419
|
):
|
@@ -381,22 +426,35 @@ class Hypern:
|
|
381
426
|
if self.scheduler:
|
382
427
|
self.scheduler.start()
|
383
428
|
|
429
|
+
server = Server()
|
430
|
+
server.set_router(router=self.router)
|
431
|
+
server.set_websocket_router(websocket_router=self.websocket_router)
|
432
|
+
server.set_injected(injected=self.injectables)
|
433
|
+
server.set_before_hooks(hooks=self.middleware_before_request)
|
434
|
+
server.set_after_hooks(hooks=self.middleware_after_request)
|
435
|
+
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
|
+
|
439
|
+
if self.database_config:
|
440
|
+
server.set_database_config(config=self.database_config)
|
441
|
+
if self.start_up_handler:
|
442
|
+
server.set_startup_handler(self.start_up_handler)
|
443
|
+
if self.shutdown_handler:
|
444
|
+
server.set_shutdown_handler(self.shutdown_handler)
|
445
|
+
|
446
|
+
if self.args.auto_workers:
|
447
|
+
self.args.workers = self.thread_config.workers
|
448
|
+
self.args.max_blocking_threads = self.thread_config.max_blocking_threads
|
449
|
+
|
384
450
|
run_processes(
|
451
|
+
server=server,
|
385
452
|
host=self.args.host,
|
386
453
|
port=self.args.port,
|
387
454
|
workers=self.args.workers,
|
388
455
|
processes=self.args.processes,
|
389
456
|
max_blocking_threads=self.args.max_blocking_threads,
|
390
|
-
router=self.router,
|
391
|
-
websocket_router=self.websocket_router,
|
392
|
-
injectables=self.injectables,
|
393
|
-
before_request=self.middleware_before_request,
|
394
|
-
after_request=self.middleware_after_request,
|
395
|
-
response_headers=self.response_headers,
|
396
457
|
reload=self.args.reload,
|
397
|
-
on_startup=self.start_up_handler,
|
398
|
-
on_shutdown=self.shutdown_handler,
|
399
|
-
auto_compression=self.args.auto_compression or self.auto_compression,
|
400
458
|
)
|
401
459
|
|
402
460
|
def add_route(self, method: HTTPMethod, endpoint: str, handler: Callable[..., Any]):
|
hypern/args_parser.py
CHANGED
@@ -55,12 +55,38 @@ class ArgsConfig:
|
|
55
55
|
action="store_true",
|
56
56
|
help="It compresses the response automatically.",
|
57
57
|
)
|
58
|
+
|
59
|
+
parser.add_argument(
|
60
|
+
"--auto-workers",
|
61
|
+
action="store_true",
|
62
|
+
help="It sets the number of workers and max-blocking-threads automatically.",
|
63
|
+
)
|
64
|
+
|
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
|
+
|
58
81
|
args, _ = parser.parse_known_args()
|
59
82
|
|
60
83
|
self.host = args.host or "127.0.0.1"
|
61
84
|
self.port = args.port or 5000
|
62
|
-
self.max_blocking_threads = args.max_blocking_threads or
|
85
|
+
self.max_blocking_threads = args.max_blocking_threads or 32
|
63
86
|
self.processes = args.processes or 1
|
64
87
|
self.workers = args.workers or 1
|
65
88
|
self.reload = args.reload or False
|
66
89
|
self.auto_compression = args.auto_compression
|
90
|
+
self.auto_workers = args.auto_workers
|
91
|
+
self.min_capacity = args.min_capacity
|
92
|
+
self.max_capacity = args.max_capacity
|
hypern/config.py
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import os
|
4
|
+
|
5
|
+
# -*- coding: utf-8 -*-
|
6
|
+
import threading
|
4
7
|
import typing
|
5
8
|
import warnings
|
9
|
+
from contextvars import ContextVar
|
10
|
+
from datetime import datetime
|
6
11
|
from pathlib import Path
|
12
|
+
from typing import Dict, Optional
|
7
13
|
|
8
14
|
"""
|
9
15
|
|
@@ -147,3 +153,94 @@ class Config:
|
|
147
153
|
return cast(value)
|
148
154
|
except (TypeError, ValueError):
|
149
155
|
raise ValueError(f"Config '{key}' has value '{value}'. Not a valid {cast.__name__}.")
|
156
|
+
|
157
|
+
|
158
|
+
class ContextStore:
|
159
|
+
def __init__(self, cleanup_interval: int = 300, max_age: int = 3600):
|
160
|
+
"""
|
161
|
+
Initialize ContextStore with automatic session cleanup.
|
162
|
+
|
163
|
+
:param cleanup_interval: Interval between cleanup checks (in seconds)
|
164
|
+
:param max_age: Maximum age of a session before it's considered expired (in seconds)
|
165
|
+
"""
|
166
|
+
self._session_times: Dict[str, datetime] = {}
|
167
|
+
self.session_var = ContextVar("session_id", default=None)
|
168
|
+
|
169
|
+
self._max_age = max_age
|
170
|
+
self._cleanup_interval = cleanup_interval
|
171
|
+
self._cleanup_thread: Optional[threading.Thread] = None
|
172
|
+
self._stop_event = threading.Event()
|
173
|
+
|
174
|
+
# Start the cleanup thread
|
175
|
+
self._start_cleanup_thread()
|
176
|
+
|
177
|
+
def _start_cleanup_thread(self):
|
178
|
+
"""Start a background thread for periodic session cleanup."""
|
179
|
+
|
180
|
+
def cleanup_worker():
|
181
|
+
while not self._stop_event.is_set():
|
182
|
+
self._perform_cleanup()
|
183
|
+
self._stop_event.wait(self._cleanup_interval)
|
184
|
+
|
185
|
+
self._cleanup_thread = threading.Thread(
|
186
|
+
target=cleanup_worker,
|
187
|
+
daemon=True, # Allows the thread to be automatically terminated when the main program exits
|
188
|
+
)
|
189
|
+
self._cleanup_thread.start()
|
190
|
+
|
191
|
+
def _perform_cleanup(self):
|
192
|
+
"""Perform cleanup of expired sessions."""
|
193
|
+
current_time = datetime.now()
|
194
|
+
expired_sessions = [
|
195
|
+
session_id for session_id, timestamp in list(self._session_times.items()) if (current_time - timestamp).total_seconds() > self._max_age
|
196
|
+
]
|
197
|
+
|
198
|
+
for session_id in expired_sessions:
|
199
|
+
self.remove_session(session_id)
|
200
|
+
|
201
|
+
def remove_session(self, session_id: str):
|
202
|
+
"""Remove a specific session."""
|
203
|
+
self._session_times.pop(session_id, None)
|
204
|
+
|
205
|
+
def set_context(self, session_id: str):
|
206
|
+
"""
|
207
|
+
Context manager for setting and resetting session context.
|
208
|
+
|
209
|
+
:param session_id: Unique identifier for the session
|
210
|
+
:return: Context manager for session
|
211
|
+
"""
|
212
|
+
self.session_var.set(session_id)
|
213
|
+
self._session_times[session_id] = datetime.now()
|
214
|
+
|
215
|
+
def get_context(self) -> str:
|
216
|
+
"""
|
217
|
+
Get the current session context.
|
218
|
+
|
219
|
+
:return: Current session ID
|
220
|
+
:raises RuntimeError: If no session context is available
|
221
|
+
"""
|
222
|
+
return self.session_var.get()
|
223
|
+
|
224
|
+
def reset_context(self):
|
225
|
+
"""Reset the session context."""
|
226
|
+
token = self.get_context()
|
227
|
+
if token is not None:
|
228
|
+
self.session_var.reset(token)
|
229
|
+
|
230
|
+
def stop_cleanup(self):
|
231
|
+
"""
|
232
|
+
Stop the cleanup thread.
|
233
|
+
Useful for graceful shutdown of the application.
|
234
|
+
"""
|
235
|
+
self._stop_event.set()
|
236
|
+
if self._cleanup_thread:
|
237
|
+
self._cleanup_thread.join()
|
238
|
+
|
239
|
+
def __del__(self):
|
240
|
+
"""
|
241
|
+
Ensure cleanup thread is stopped when the object is deleted.
|
242
|
+
"""
|
243
|
+
self.stop_cleanup()
|
244
|
+
|
245
|
+
|
246
|
+
context_store = ContextStore()
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
import asyncio
|
3
|
+
import traceback
|
4
|
+
from contextlib import asynccontextmanager
|
5
|
+
|
6
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_scoped_session
|
7
|
+
from sqlalchemy.orm import Session, sessionmaker
|
8
|
+
from sqlalchemy.sql.expression import Delete, Insert, Update
|
9
|
+
|
10
|
+
from .repository import Model, PostgresRepository
|
11
|
+
|
12
|
+
|
13
|
+
class SqlalchemyConfig:
|
14
|
+
def __init__(self, default_engine: AsyncEngine | None = None, reader_engine: AsyncEngine | None = None, writer_engine: AsyncEngine | None = None):
|
15
|
+
"""
|
16
|
+
Initialize the SQL configuration.
|
17
|
+
You can provide a default engine, a reader engine, and a writer engine.
|
18
|
+
If only one engine is provided (default_engine), it will be used for both reading and writing.
|
19
|
+
If both reader and writer engines are provided, they will be used for reading and writing respectively.
|
20
|
+
Note: The reader and writer engines must be different.
|
21
|
+
"""
|
22
|
+
|
23
|
+
assert default_engine or reader_engine or writer_engine, "At least one engine must be provided."
|
24
|
+
assert not (reader_engine and writer_engine and id(reader_engine) == id(writer_engine)), "Reader and writer engines must be different."
|
25
|
+
|
26
|
+
engines = {
|
27
|
+
"writer": writer_engine or default_engine,
|
28
|
+
"reader": reader_engine or default_engine,
|
29
|
+
}
|
30
|
+
|
31
|
+
class RoutingSession(Session):
|
32
|
+
def get_bind(this, mapper=None, clause=None, **kwargs):
|
33
|
+
if this._flushing or isinstance(clause, (Update, Delete, Insert)):
|
34
|
+
return engines["writer"].sync_engine
|
35
|
+
return engines["reader"].sync_engine
|
36
|
+
|
37
|
+
async_session_factory = sessionmaker(
|
38
|
+
class_=AsyncSession,
|
39
|
+
sync_session_class=RoutingSession,
|
40
|
+
expire_on_commit=False,
|
41
|
+
)
|
42
|
+
|
43
|
+
session_scope: AsyncSession | async_scoped_session = async_scoped_session(
|
44
|
+
session_factory=async_session_factory,
|
45
|
+
scopefunc=asyncio.current_task,
|
46
|
+
)
|
47
|
+
|
48
|
+
@asynccontextmanager
|
49
|
+
async def get_session():
|
50
|
+
"""
|
51
|
+
Get the database session.
|
52
|
+
This can be used for dependency injection.
|
53
|
+
|
54
|
+
:return: The database session.
|
55
|
+
"""
|
56
|
+
try:
|
57
|
+
yield session_scope
|
58
|
+
except Exception:
|
59
|
+
traceback.print_exc()
|
60
|
+
await session_scope.rollback()
|
61
|
+
finally:
|
62
|
+
await session_scope.remove()
|
63
|
+
await session_scope.close()
|
64
|
+
|
65
|
+
self.get_session = get_session
|
66
|
+
|
67
|
+
def init_app(self, app):
|
68
|
+
app.inject("get_session", self.get_session)
|
69
|
+
|
70
|
+
|
71
|
+
__all__ = ["Model", "PostgresRepository", "SqlalchemyConfig"]
|
hypern/db/sql/__init__.py
CHANGED
@@ -1,179 +1,13 @@
|
|
1
|
-
#
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
from hypern.hypern import Request, Response
|
16
|
-
|
17
|
-
from .repository import Model, PostgresRepository
|
18
|
-
|
19
|
-
|
20
|
-
class ContextStore:
|
21
|
-
def __init__(self, cleanup_interval: int = 300, max_age: int = 3600):
|
22
|
-
"""
|
23
|
-
Initialize ContextStore with automatic session cleanup.
|
24
|
-
|
25
|
-
:param cleanup_interval: Interval between cleanup checks (in seconds)
|
26
|
-
:param max_age: Maximum age of a session before it's considered expired (in seconds)
|
27
|
-
"""
|
28
|
-
self._session_times: Dict[str, datetime] = {}
|
29
|
-
self.session_var = ContextVar("session_id", default=None)
|
30
|
-
|
31
|
-
self._max_age = max_age
|
32
|
-
self._cleanup_interval = cleanup_interval
|
33
|
-
self._cleanup_thread: Optional[threading.Thread] = None
|
34
|
-
self._stop_event = threading.Event()
|
35
|
-
|
36
|
-
# Start the cleanup thread
|
37
|
-
self._start_cleanup_thread()
|
38
|
-
|
39
|
-
def _start_cleanup_thread(self):
|
40
|
-
"""Start a background thread for periodic session cleanup."""
|
41
|
-
|
42
|
-
def cleanup_worker():
|
43
|
-
while not self._stop_event.is_set():
|
44
|
-
self._perform_cleanup()
|
45
|
-
self._stop_event.wait(self._cleanup_interval)
|
46
|
-
|
47
|
-
self._cleanup_thread = threading.Thread(
|
48
|
-
target=cleanup_worker,
|
49
|
-
daemon=True, # Allows the thread to be automatically terminated when the main program exits
|
50
|
-
)
|
51
|
-
self._cleanup_thread.start()
|
52
|
-
|
53
|
-
def _perform_cleanup(self):
|
54
|
-
"""Perform cleanup of expired sessions."""
|
55
|
-
current_time = datetime.now()
|
56
|
-
expired_sessions = [
|
57
|
-
session_id for session_id, timestamp in list(self._session_times.items()) if (current_time - timestamp).total_seconds() > self._max_age
|
58
|
-
]
|
59
|
-
|
60
|
-
for session_id in expired_sessions:
|
61
|
-
self.remove_session(session_id)
|
62
|
-
|
63
|
-
def remove_session(self, session_id: str):
|
64
|
-
"""Remove a specific session."""
|
65
|
-
self._session_times.pop(session_id, None)
|
66
|
-
|
67
|
-
def set_context(self, session_id: str):
|
68
|
-
"""
|
69
|
-
Context manager for setting and resetting session context.
|
70
|
-
|
71
|
-
:param session_id: Unique identifier for the session
|
72
|
-
:return: Context manager for session
|
73
|
-
"""
|
74
|
-
self.session_var.set(session_id)
|
75
|
-
self._session_times[session_id] = datetime.now()
|
76
|
-
|
77
|
-
def get_context(self) -> str:
|
78
|
-
"""
|
79
|
-
Get the current session context.
|
80
|
-
|
81
|
-
:return: Current session ID
|
82
|
-
:raises RuntimeError: If no session context is available
|
83
|
-
"""
|
84
|
-
return self.session_var.get()
|
85
|
-
|
86
|
-
def reset_context(self):
|
87
|
-
"""Reset the session context."""
|
88
|
-
token = self.get_context()
|
89
|
-
if token is not None:
|
90
|
-
self.session_var.reset(token)
|
91
|
-
|
92
|
-
def stop_cleanup(self):
|
93
|
-
"""
|
94
|
-
Stop the cleanup thread.
|
95
|
-
Useful for graceful shutdown of the application.
|
96
|
-
"""
|
97
|
-
self._stop_event.set()
|
98
|
-
if self._cleanup_thread:
|
99
|
-
self._cleanup_thread.join()
|
100
|
-
|
101
|
-
def __del__(self):
|
102
|
-
"""
|
103
|
-
Ensure cleanup thread is stopped when the object is deleted.
|
104
|
-
"""
|
105
|
-
self.stop_cleanup()
|
106
|
-
|
107
|
-
|
108
|
-
class SqlConfig:
|
109
|
-
def __init__(self, default_engine: AsyncEngine | None = None, reader_engine: AsyncEngine | None = None, writer_engine: AsyncEngine | None = None):
|
110
|
-
"""
|
111
|
-
Initialize the SQL configuration.
|
112
|
-
You can provide a default engine, a reader engine, and a writer engine.
|
113
|
-
If only one engine is provided (default_engine), it will be used for both reading and writing.
|
114
|
-
If both reader and writer engines are provided, they will be used for reading and writing respectively.
|
115
|
-
Note: The reader and writer engines must be different.
|
116
|
-
"""
|
117
|
-
|
118
|
-
assert default_engine or reader_engine or writer_engine, "At least one engine must be provided."
|
119
|
-
assert not (reader_engine and writer_engine and id(reader_engine) == id(writer_engine)), "Reader and writer engines must be different."
|
120
|
-
|
121
|
-
engines = {
|
122
|
-
"writer": writer_engine or default_engine,
|
123
|
-
"reader": reader_engine or default_engine,
|
124
|
-
}
|
125
|
-
self.session_store = ContextStore()
|
126
|
-
|
127
|
-
class RoutingSession(Session):
|
128
|
-
def get_bind(this, mapper=None, clause=None, **kwargs):
|
129
|
-
if this._flushing or isinstance(clause, (Update, Delete, Insert)):
|
130
|
-
return engines["writer"].sync_engine
|
131
|
-
return engines["reader"].sync_engine
|
132
|
-
|
133
|
-
async_session_factory = sessionmaker(
|
134
|
-
class_=AsyncSession,
|
135
|
-
sync_session_class=RoutingSession,
|
136
|
-
expire_on_commit=False,
|
137
|
-
)
|
138
|
-
|
139
|
-
session_scope: Union[AsyncSession, async_scoped_session] = async_scoped_session(
|
140
|
-
session_factory=async_session_factory,
|
141
|
-
scopefunc=asyncio.current_task,
|
142
|
-
)
|
143
|
-
|
144
|
-
@asynccontextmanager
|
145
|
-
async def get_session():
|
146
|
-
"""
|
147
|
-
Get the database session.
|
148
|
-
This can be used for dependency injection.
|
149
|
-
|
150
|
-
:return: The database session.
|
151
|
-
"""
|
152
|
-
try:
|
153
|
-
yield session_scope
|
154
|
-
except Exception:
|
155
|
-
traceback.print_exc()
|
156
|
-
await session_scope.rollback()
|
157
|
-
finally:
|
158
|
-
await session_scope.remove()
|
159
|
-
await session_scope.close()
|
160
|
-
|
161
|
-
self.get_session = get_session
|
162
|
-
self._context_token: Optional[Token] = None
|
163
|
-
|
164
|
-
def before_request(self, request: Request):
|
165
|
-
token = str(uuid4())
|
166
|
-
self.session_store.set_context(token)
|
167
|
-
return request
|
168
|
-
|
169
|
-
def after_request(self, response: Response):
|
170
|
-
self.session_store.reset_context()
|
171
|
-
return response
|
172
|
-
|
173
|
-
def init_app(self, app):
|
174
|
-
app.inject("get_session", self.get_session)
|
175
|
-
app.before_request()(self.before_request)
|
176
|
-
app.after_request()(self.after_request)
|
177
|
-
|
178
|
-
|
179
|
-
__all__ = ["Model", "PostgresRepository", "SqlConfig"]
|
1
|
+
# from .context import SqlConfig, DatabaseType
|
2
|
+
from .field import CharField, IntegerField
|
3
|
+
from .model import Model
|
4
|
+
from .query import F, Q, QuerySet
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"CharField",
|
8
|
+
"IntegerField",
|
9
|
+
"Model",
|
10
|
+
"Q",
|
11
|
+
"F",
|
12
|
+
"QuerySet",
|
13
|
+
]
|