xitzin 0.1.3__py3-none-any.whl → 0.2.0__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.
xitzin/sqlmodel.py ADDED
@@ -0,0 +1,172 @@
1
+ """SQLModel integration for Xitzin.
2
+
3
+ This module provides database session management through middleware
4
+ and helper functions. Requires the 'sqlmodel' optional dependency.
5
+
6
+ Install with: pip install xitzin[sqlmodel]
7
+
8
+ Example:
9
+ from sqlmodel import Field, select
10
+ from xitzin import Xitzin, Request
11
+ from xitzin.sqlmodel import (
12
+ SQLModel,
13
+ create_engine,
14
+ SessionMiddleware,
15
+ get_session,
16
+ init_db,
17
+ )
18
+
19
+ # Define models
20
+ class Entry(SQLModel, table=True):
21
+ id: int | None = Field(default=None, primary_key=True)
22
+ content: str
23
+
24
+ # Setup app
25
+ app = Xitzin()
26
+ engine = create_engine("sqlite:///./database.db")
27
+
28
+ # Initialize database and add middleware
29
+ init_db(app, engine)
30
+ app.middleware(SessionMiddleware(engine))
31
+
32
+ # Use in routes
33
+ @app.gemini("/entries")
34
+ def list_entries(request: Request):
35
+ session = get_session(request)
36
+ entries = session.exec(select(Entry)).all()
37
+ return render_entries(entries)
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ from typing import TYPE_CHECKING, Awaitable, Callable
43
+
44
+ from sqlalchemy import Engine
45
+ from sqlmodel import Session, SQLModel, create_engine
46
+
47
+ if TYPE_CHECKING:
48
+ from nauyaca.protocol.response import GeminiResponse
49
+
50
+ from xitzin.application import Xitzin
51
+ from xitzin.requests import Request
52
+
53
+ __all__ = [
54
+ "SQLModel",
55
+ "Session",
56
+ "create_engine",
57
+ "SessionMiddleware",
58
+ "get_session",
59
+ "init_db",
60
+ ]
61
+
62
+
63
+ def SessionMiddleware(
64
+ engine: Engine,
65
+ *,
66
+ autoflush: bool = True,
67
+ ) -> Callable[
68
+ ["Request", Callable[["Request"], Awaitable["GeminiResponse"]]],
69
+ Awaitable["GeminiResponse"],
70
+ ]:
71
+ """Create a middleware that manages database sessions per request.
72
+
73
+ The session is stored in request.state.db and automatically committed
74
+ on success or rolled back on error.
75
+
76
+ Args:
77
+ engine: SQLAlchemy engine instance.
78
+ autoflush: If True, flush before queries. Defaults to True.
79
+
80
+ Returns:
81
+ Middleware function compatible with @app.middleware.
82
+
83
+ Example:
84
+ engine = create_engine("sqlite:///./database.db")
85
+ app.middleware(SessionMiddleware(engine))
86
+ """
87
+
88
+ async def middleware(
89
+ request: "Request",
90
+ call_next: Callable[["Request"], Awaitable["GeminiResponse"]],
91
+ ) -> "GeminiResponse":
92
+ session = Session(engine, autoflush=autoflush)
93
+ request.state.db = session
94
+
95
+ try:
96
+ response = await call_next(request)
97
+ session.commit()
98
+ return response
99
+ except Exception:
100
+ session.rollback()
101
+ raise
102
+ finally:
103
+ session.close()
104
+
105
+ return middleware
106
+
107
+
108
+ def get_session(request: "Request") -> Session:
109
+ """Get the database session from the current request.
110
+
111
+ This helper retrieves the session created by SessionMiddleware.
112
+
113
+ Args:
114
+ request: The current Xitzin request object.
115
+
116
+ Returns:
117
+ SQLModel Session instance.
118
+
119
+ Raises:
120
+ AttributeError: If SessionMiddleware is not configured.
121
+
122
+ Example:
123
+ @app.gemini("/users/{user_id}")
124
+ def get_user(request: Request, user_id: int):
125
+ session = get_session(request)
126
+ user = session.get(User, user_id)
127
+ return f"# {user.name}"
128
+ """
129
+ if not hasattr(request.state, "db"):
130
+ raise AttributeError(
131
+ "No database session found. Did you add SessionMiddleware?"
132
+ )
133
+ return request.state.db
134
+
135
+
136
+ def init_db(
137
+ app: "Xitzin",
138
+ engine: Engine,
139
+ *,
140
+ create_tables: bool = True,
141
+ drop_all: bool = False,
142
+ ) -> None:
143
+ """Initialize database with lifecycle hooks.
144
+
145
+ This helper registers startup/shutdown hooks to manage table creation
146
+ and engine cleanup.
147
+
148
+ Args:
149
+ app: Xitzin application instance.
150
+ engine: SQLAlchemy engine instance.
151
+ create_tables: Create all tables on startup. Defaults to True.
152
+ drop_all: Drop all tables before creating. Defaults to False.
153
+
154
+ Warning:
155
+ Setting drop_all=True will DELETE ALL DATA on startup!
156
+
157
+ Example:
158
+ app = Xitzin()
159
+ engine = create_engine("sqlite:///./database.db")
160
+ init_db(app, engine)
161
+ """
162
+
163
+ @app.on_startup
164
+ def create_db_tables() -> None:
165
+ if drop_all:
166
+ SQLModel.metadata.drop_all(engine)
167
+ if create_tables:
168
+ SQLModel.metadata.create_all(engine)
169
+
170
+ @app.on_shutdown
171
+ def dispose_engine() -> None:
172
+ engine.dispose()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: xitzin
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: A Gemini Application Framework
5
5
  Keywords: gemini,protocol,framework,async,geminispace
6
6
  Author: Alan Velasco
@@ -21,12 +21,14 @@ Requires-Dist: jinja2>=3.1.0
21
21
  Requires-Dist: nauyaca>=0.3.2
22
22
  Requires-Dist: rich>=14.2.0
23
23
  Requires-Dist: typing-extensions>=4.15.0
24
+ Requires-Dist: sqlmodel>=0.0.22 ; extra == 'sqlmodel'
24
25
  Requires-Python: >=3.10
25
26
  Project-URL: Changelog, https://xitzin.readthedocs.io/changelog/
26
27
  Project-URL: Documentation, https://xitzin.readthedocs.io
27
28
  Project-URL: Homepage, https://github.com/alanbato/xitzin
28
29
  Project-URL: Issues, https://github.com/alanbato/xitzin/issues
29
30
  Project-URL: Repository, https://github.com/alanbato/xitzin.git
31
+ Provides-Extra: sqlmodel
30
32
  Description-Content-Type: text/markdown
31
33
 
32
34
  # Xitzin
@@ -8,8 +8,9 @@ xitzin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  xitzin/requests.py,sha256=EeDqh0roz7eTItqarEDj53iFxMZlVsEIcimybKdqAHI,4612
9
9
  xitzin/responses.py,sha256=N4HeP9Yy2PIHY0zsGa2pj8xvX2OFHAjtqR5nogNh8UQ,6545
10
10
  xitzin/routing.py,sha256=SiT9J617GfQR_rPDD3Ivw0_N4CTh8-Jb_AZjWJnT5sw,12409
11
+ xitzin/sqlmodel.py,sha256=lZvDzYAgnG8S2K-EYnx6PkO7D68pjM06uQLSKUZ9yY4,4500
11
12
  xitzin/templating.py,sha256=spjxb05wgwKA4txOMbWJuwEVMaoLovo13_ukbXAcBDY,6040
12
13
  xitzin/testing.py,sha256=JO41TeIJeb1CqHVqBOjCVAvv9BOlvDJYzAeG83ZofdE,7572
13
- xitzin-0.1.3.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
14
- xitzin-0.1.3.dist-info/METADATA,sha256=0K_lXt3a_Vy_itxhTC7hbbQPLEZ4dqMUhNhg3UqIDjs,3272
15
- xitzin-0.1.3.dist-info/RECORD,,
14
+ xitzin-0.2.0.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
15
+ xitzin-0.2.0.dist-info/METADATA,sha256=Jvf0vYB5goyuimrOa4321LhntdLNJKxIJCUpuIyTmbQ,3351
16
+ xitzin-0.2.0.dist-info/RECORD,,
File without changes