skrift 0.1.0a16__py3-none-any.whl → 0.1.0a17__py3-none-any.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.
- skrift/asgi.py +6 -2
- skrift/config.py +1 -0
- skrift/db/session.py +90 -0
- skrift/setup/config_writer.py +1 -0
- skrift/setup/state.py +4 -4
- {skrift-0.1.0a16.dist-info → skrift-0.1.0a17.dist-info}/METADATA +1 -1
- {skrift-0.1.0a16.dist-info → skrift-0.1.0a17.dist-info}/RECORD +9 -8
- {skrift-0.1.0a16.dist-info → skrift-0.1.0a17.dist-info}/WHEEL +0 -0
- {skrift-0.1.0a16.dist-info → skrift-0.1.0a17.dist-info}/entry_points.txt +0 -0
skrift/asgi.py
CHANGED
|
@@ -22,6 +22,8 @@ from advanced_alchemy.extensions.litestar import (
|
|
|
22
22
|
SQLAlchemyAsyncConfig,
|
|
23
23
|
SQLAlchemyPlugin,
|
|
24
24
|
)
|
|
25
|
+
|
|
26
|
+
from skrift.db.session import SafeSQLAlchemyAsyncConfig
|
|
25
27
|
from litestar import Litestar
|
|
26
28
|
from litestar.config.compression import CompressionConfig
|
|
27
29
|
from litestar.contrib.jinja import JinjaTemplateEngine
|
|
@@ -398,10 +400,11 @@ def create_app() -> Litestar:
|
|
|
398
400
|
pool_size=settings.db.pool_size,
|
|
399
401
|
max_overflow=settings.db.pool_overflow,
|
|
400
402
|
pool_timeout=settings.db.pool_timeout,
|
|
403
|
+
pool_pre_ping=settings.db.pool_pre_ping,
|
|
401
404
|
echo=settings.db.echo,
|
|
402
405
|
)
|
|
403
406
|
|
|
404
|
-
db_config =
|
|
407
|
+
db_config = SafeSQLAlchemyAsyncConfig(
|
|
405
408
|
connection_string=settings.db.url,
|
|
406
409
|
metadata=Base.metadata,
|
|
407
410
|
create_all=False,
|
|
@@ -564,10 +567,11 @@ def create_setup_app() -> Litestar:
|
|
|
564
567
|
pool_size=5,
|
|
565
568
|
max_overflow=10,
|
|
566
569
|
pool_timeout=30,
|
|
570
|
+
pool_pre_ping=True,
|
|
567
571
|
echo=False,
|
|
568
572
|
)
|
|
569
573
|
|
|
570
|
-
db_config =
|
|
574
|
+
db_config = SafeSQLAlchemyAsyncConfig(
|
|
571
575
|
connection_string=db_url,
|
|
572
576
|
metadata=Base.metadata,
|
|
573
577
|
create_all=False,
|
skrift/config.py
CHANGED
skrift/db/session.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Safe SQLAlchemy async session provider with CancelledError handling.
|
|
2
|
+
|
|
3
|
+
This module provides a custom session configuration that properly handles
|
|
4
|
+
connection cleanup when HTTP requests are cancelled (client disconnect, timeout).
|
|
5
|
+
Without this, CancelledError can prevent session cleanup, leading to connection
|
|
6
|
+
pool leaks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from collections.abc import AsyncGenerator
|
|
11
|
+
from typing import TYPE_CHECKING, Callable, cast
|
|
12
|
+
|
|
13
|
+
from advanced_alchemy._listeners import set_async_context
|
|
14
|
+
from advanced_alchemy.extensions.litestar import SQLAlchemyAsyncConfig
|
|
15
|
+
from advanced_alchemy.extensions.litestar._utils import (
|
|
16
|
+
delete_aa_scope_state,
|
|
17
|
+
get_aa_scope_state,
|
|
18
|
+
set_aa_scope_state,
|
|
19
|
+
)
|
|
20
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from litestar.datastructures import State
|
|
24
|
+
from litestar.types import Scope
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SafeSQLAlchemyAsyncConfig(SQLAlchemyAsyncConfig):
|
|
28
|
+
"""SQLAlchemy async config with safe session cleanup on request cancellation.
|
|
29
|
+
|
|
30
|
+
This subclass overrides `provide_session` to use an async generator that
|
|
31
|
+
catches CancelledError and ensures sessions are properly closed, preventing
|
|
32
|
+
connection pool leaks when HTTP requests are cancelled.
|
|
33
|
+
|
|
34
|
+
The standard advanced_alchemy session management relies on ASGI events
|
|
35
|
+
(http.response.body, http.disconnect) to trigger cleanup via before_send_handler.
|
|
36
|
+
However, CancelledError can prevent these events from firing, leaving sessions
|
|
37
|
+
in an unclosed state.
|
|
38
|
+
|
|
39
|
+
By using an async generator, Litestar's dependency injection system ensures
|
|
40
|
+
cleanup runs even when CancelledError is raised.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
async def provide_session(
|
|
44
|
+
self,
|
|
45
|
+
state: "State",
|
|
46
|
+
scope: "Scope",
|
|
47
|
+
) -> AsyncGenerator[AsyncSession, None]:
|
|
48
|
+
"""Provide a database session with proper cleanup on cancellation.
|
|
49
|
+
|
|
50
|
+
This async generator wraps session creation to ensure that
|
|
51
|
+
CancelledError (raised when an HTTP request is cancelled) doesn't
|
|
52
|
+
prevent session cleanup, which would leak connections.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
state: The application state
|
|
56
|
+
scope: The ASGI scope
|
|
57
|
+
|
|
58
|
+
Yields:
|
|
59
|
+
AsyncSession: The database session
|
|
60
|
+
"""
|
|
61
|
+
# Check if we already have a session in scope
|
|
62
|
+
session = cast(
|
|
63
|
+
"AsyncSession | None",
|
|
64
|
+
get_aa_scope_state(scope, self.session_scope_key),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if session is None:
|
|
68
|
+
# Create a new session
|
|
69
|
+
session_maker = cast(
|
|
70
|
+
"Callable[[], AsyncSession]",
|
|
71
|
+
state[self.session_maker_app_state_key],
|
|
72
|
+
)
|
|
73
|
+
session = session_maker()
|
|
74
|
+
# Store in scope for reuse within this request
|
|
75
|
+
set_aa_scope_state(scope, self.session_scope_key, session)
|
|
76
|
+
|
|
77
|
+
set_async_context(True)
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
yield session
|
|
81
|
+
except asyncio.CancelledError:
|
|
82
|
+
# Request was cancelled - ensure we clean up the session
|
|
83
|
+
# This prevents connection pool leaks
|
|
84
|
+
await session.close()
|
|
85
|
+
# Remove the session from scope state to prevent double-close
|
|
86
|
+
delete_aa_scope_state(scope, self.session_scope_key)
|
|
87
|
+
raise
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
__all__ = ["SafeSQLAlchemyAsyncConfig"]
|
skrift/setup/config_writer.py
CHANGED
skrift/setup/state.py
CHANGED
|
@@ -172,9 +172,9 @@ def run_migrations_if_needed() -> tuple[bool, str | None]:
|
|
|
172
172
|
return True, None
|
|
173
173
|
|
|
174
174
|
try:
|
|
175
|
-
# Try skrift
|
|
175
|
+
# Try skrift db first (the correct command)
|
|
176
176
|
result = subprocess.run(
|
|
177
|
-
["skrift
|
|
177
|
+
["skrift", "db", "upgrade", "head"],
|
|
178
178
|
capture_output=True,
|
|
179
179
|
text=True,
|
|
180
180
|
cwd=Path.cwd(),
|
|
@@ -183,7 +183,7 @@ def run_migrations_if_needed() -> tuple[bool, str | None]:
|
|
|
183
183
|
if result.returncode == 0:
|
|
184
184
|
_migrations_run = True
|
|
185
185
|
return True, None
|
|
186
|
-
# If skrift
|
|
186
|
+
# If skrift db fails, try alembic directly
|
|
187
187
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
188
188
|
pass
|
|
189
189
|
|
|
@@ -202,7 +202,7 @@ def run_migrations_if_needed() -> tuple[bool, str | None]:
|
|
|
202
202
|
except subprocess.TimeoutExpired:
|
|
203
203
|
return False, "Migration timed out"
|
|
204
204
|
except FileNotFoundError:
|
|
205
|
-
return False, "
|
|
205
|
+
return False, "skrift db command not found"
|
|
206
206
|
except Exception as e:
|
|
207
207
|
return False, str(e)
|
|
208
208
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
skrift/__init__.py,sha256=eXE5PFVkJpH5XsV_ZlrTIeFPUPrmcHYAj4GpRS3R5PY,29
|
|
2
2
|
skrift/__main__.py,sha256=wt6JZL9nBhKU36vdyurhOEtWy7w3C9zohyy24PLcKho,164
|
|
3
3
|
skrift/alembic.ini,sha256=mYguI6CbMCTyfHctsGiTyf9Z5gv21FdeI3qtfgOHO3A,1815
|
|
4
|
-
skrift/asgi.py,sha256=
|
|
4
|
+
skrift/asgi.py,sha256=re_DzFiLzFdSiY3TAj_UGuVedkZTIs8jx1rHb6vPvkM,23509
|
|
5
5
|
skrift/cli.py,sha256=-K-uO1r8y5r8wsRGygZFyaH1DsY1s9L0XViScPBnt9s,5744
|
|
6
|
-
skrift/config.py,sha256=
|
|
6
|
+
skrift/config.py,sha256=9qDy9GhGbUZWinsagfPPkeAWsapHoZmqmeYFR3CEdxQ,7928
|
|
7
7
|
skrift/admin/__init__.py,sha256=x81Cj_ilVmv6slaMl16HHyT_AgrnLxKEWkS0RPa4V9s,289
|
|
8
8
|
skrift/admin/controller.py,sha256=gSprerjhxqxaH2lHm8vbLuz-jslN553FpbKF7jwEm9Y,19607
|
|
9
9
|
skrift/admin/navigation.py,sha256=VwttFoIUIJy5rONKIkJd5w4CNkUpeK22_OfLGHecN34,3382
|
|
@@ -34,6 +34,7 @@ skrift/controllers/sitemap.py,sha256=Yfz2ElHrkZVMhAN5ixriU5RwEFJ713rS0Uezj90Bfts
|
|
|
34
34
|
skrift/controllers/web.py,sha256=J-kbmzwJcKfiBvNbF8yR7GHGo0jhvWzY77YstJi_pcI,2968
|
|
35
35
|
skrift/db/__init__.py,sha256=uSghyDFT2K4SFiEqUzdjCGzWpS-Oy6Sd1FUappau-v0,52
|
|
36
36
|
skrift/db/base.py,sha256=QJplFj9235kZdScASEpvyNHln6YW2hqbHwJEYZ3OSsc,173
|
|
37
|
+
skrift/db/session.py,sha256=b2E4VeSIscPTEDBWQrketJbmUqLdWqdhqhUv7cojUkY,3237
|
|
37
38
|
skrift/db/models/__init__.py,sha256=DtgYPLs8rTLNFmZ0-HzD_8uhvaGRtayZCrxpMhAo88U,413
|
|
38
39
|
skrift/db/models/oauth_account.py,sha256=fzmApdtrRYeEWLl35Y7H0YtF7CWjzrpG-lknBOuYRc8,1840
|
|
39
40
|
skrift/db/models/page.py,sha256=pYMWBmQ7FfDiFcyBU3xV4kbpWboimZw5TZtVDOcr73o,2068
|
|
@@ -54,11 +55,11 @@ skrift/lib/markdown.py,sha256=fPLUIAsmFXC64Os2RngQG7P2xRKBd1RO8fh7b5_gV1U,950
|
|
|
54
55
|
skrift/lib/seo.py,sha256=3fAfXn_7Jpq_OhINpcbQznixLcJTbaD-I86_Je2SFoo,2512
|
|
55
56
|
skrift/lib/template.py,sha256=4_urkRfvth75yNeQ5TyGTHvkvs3vVef7TcwZx0k285k,4226
|
|
56
57
|
skrift/setup/__init__.py,sha256=3VjFPMES5y0M5cQ9R4C1xazqiEPEDqTPjX9-3rBMXnA,478
|
|
57
|
-
skrift/setup/config_writer.py,sha256=
|
|
58
|
+
skrift/setup/config_writer.py,sha256=2yXwJ5TRcRnQAiRL2i0cyJ3KYnJJ-QUNUsCuSOCR7M0,6378
|
|
58
59
|
skrift/setup/controller.py,sha256=v0Ey8T7ptJ5A3vOqQ1TUAXH1bQwA0288J5uyUWMihsw,34250
|
|
59
60
|
skrift/setup/middleware.py,sha256=Nai8ZG2vHldngmAhq7kWzAwKRNcP5tHKhJHa5dCh404,2941
|
|
60
61
|
skrift/setup/providers.py,sha256=0BFKB6168NcmtXxFF6ofHgEDMQD2FbXkexsqrARVtDI,7967
|
|
61
|
-
skrift/setup/state.py,sha256=
|
|
62
|
+
skrift/setup/state.py,sha256=3RJzdtRY-ZtTWsb9aLTw43lEvgnanXMAEhK7QQmaAU0,9321
|
|
62
63
|
skrift/static/css/style.css,sha256=6in8KjmwythfQV9pGt-uSCJCoHG1OijrdDOOad7oYBk,22978
|
|
63
64
|
skrift/templates/base.html,sha256=s6gylukPyBvb9StgNz8MRZr4h3QT3C8GTMiwOu23nC0,4421
|
|
64
65
|
skrift/templates/error-404.html,sha256=sJrDaF3Or3Nyki8mxo3wBxLLzgy4wkB9p9wdS8pRA6k,409
|
|
@@ -84,7 +85,7 @@ skrift/templates/setup/configuring.html,sha256=2KHW9h2BrJgL_kO5IizbAYs4pnFLyRf76
|
|
|
84
85
|
skrift/templates/setup/database.html,sha256=gU4-315-QraHa2Eq4Fh3b55QpOM2CkJzh27_Yz13frA,5495
|
|
85
86
|
skrift/templates/setup/restart.html,sha256=GHg31F_e2uLFhWUzJoalk0Y0oYLqsFWyZXWKX3mblbY,1355
|
|
86
87
|
skrift/templates/setup/site.html,sha256=PSOH-q1-ZBl47iSW9-Ad6lEfJn_fzdGD3Pk4vb3xgK4,1680
|
|
87
|
-
skrift-0.1.
|
|
88
|
-
skrift-0.1.
|
|
89
|
-
skrift-0.1.
|
|
90
|
-
skrift-0.1.
|
|
88
|
+
skrift-0.1.0a17.dist-info/METADATA,sha256=TElTCDYHVD_CoTOzfB9htMyL2UfBHX2qYs28IIp3zt4,7267
|
|
89
|
+
skrift-0.1.0a17.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
90
|
+
skrift-0.1.0a17.dist-info/entry_points.txt,sha256=uquZ5Mumqr0xwYTpTcNiJtFSITGfF6_QCCy2DZJSZig,42
|
|
91
|
+
skrift-0.1.0a17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|