sqliter-py 0.9.0__py3-none-any.whl → 0.16.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.
- sqliter/constants.py +4 -3
- sqliter/exceptions.py +43 -0
- sqliter/model/__init__.py +38 -3
- sqliter/model/foreign_key.py +153 -0
- sqliter/model/model.py +42 -3
- sqliter/model/unique.py +20 -11
- sqliter/orm/__init__.py +16 -0
- sqliter/orm/fields.py +412 -0
- sqliter/orm/foreign_key.py +8 -0
- sqliter/orm/model.py +243 -0
- sqliter/orm/query.py +221 -0
- sqliter/orm/registry.py +169 -0
- sqliter/query/query.py +720 -69
- sqliter/sqliter.py +533 -76
- sqliter/tui/__init__.py +62 -0
- sqliter/tui/__main__.py +6 -0
- sqliter/tui/app.py +179 -0
- sqliter/tui/demos/__init__.py +96 -0
- sqliter/tui/demos/base.py +114 -0
- sqliter/tui/demos/caching.py +283 -0
- sqliter/tui/demos/connection.py +150 -0
- sqliter/tui/demos/constraints.py +211 -0
- sqliter/tui/demos/crud.py +154 -0
- sqliter/tui/demos/errors.py +231 -0
- sqliter/tui/demos/field_selection.py +150 -0
- sqliter/tui/demos/filters.py +389 -0
- sqliter/tui/demos/models.py +248 -0
- sqliter/tui/demos/ordering.py +156 -0
- sqliter/tui/demos/orm.py +460 -0
- sqliter/tui/demos/results.py +241 -0
- sqliter/tui/demos/string_filters.py +210 -0
- sqliter/tui/demos/timestamps.py +126 -0
- sqliter/tui/demos/transactions.py +177 -0
- sqliter/tui/runner.py +116 -0
- sqliter/tui/styles/app.tcss +130 -0
- sqliter/tui/widgets/__init__.py +7 -0
- sqliter/tui/widgets/code_display.py +81 -0
- sqliter/tui/widgets/demo_list.py +65 -0
- sqliter/tui/widgets/output_display.py +92 -0
- {sqliter_py-0.9.0.dist-info → sqliter_py-0.16.0.dist-info}/METADATA +27 -11
- sqliter_py-0.16.0.dist-info/RECORD +47 -0
- {sqliter_py-0.9.0.dist-info → sqliter_py-0.16.0.dist-info}/WHEEL +2 -2
- sqliter_py-0.16.0.dist-info/entry_points.txt +3 -0
- sqliter_py-0.9.0.dist-info/RECORD +0 -14
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""Query Builder filter demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from sqliter import SqliterDB
|
|
9
|
+
from sqliter.model import BaseDBModel
|
|
10
|
+
from sqliter.tui.demos.base import Demo, DemoCategory, extract_demo_code
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _run_equals() -> str:
|
|
14
|
+
"""Filter records where a field exactly matches a value.
|
|
15
|
+
|
|
16
|
+
Use __eq to find records with the specified exact value.
|
|
17
|
+
"""
|
|
18
|
+
output = io.StringIO()
|
|
19
|
+
|
|
20
|
+
class User(BaseDBModel):
|
|
21
|
+
name: str
|
|
22
|
+
age: int
|
|
23
|
+
|
|
24
|
+
db = SqliterDB(memory=True)
|
|
25
|
+
db.create_table(User)
|
|
26
|
+
|
|
27
|
+
db.insert(User(name="Alice", age=30))
|
|
28
|
+
db.insert(User(name="Bob", age=25))
|
|
29
|
+
db.insert(User(name="Alice", age=35))
|
|
30
|
+
|
|
31
|
+
results = db.select(User).filter(name__eq="Alice").fetch_all()
|
|
32
|
+
output.write(f"Found {len(results)} users named 'Alice':\n")
|
|
33
|
+
for user in results:
|
|
34
|
+
output.write(f" - {user.name}, age {user.age}\n")
|
|
35
|
+
|
|
36
|
+
db.close()
|
|
37
|
+
return output.getvalue()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_comparison() -> str:
|
|
41
|
+
"""Filter records using comparison operators.
|
|
42
|
+
|
|
43
|
+
Use __gt, __lt, __gte, __lte for greater/less than filtering.
|
|
44
|
+
"""
|
|
45
|
+
output = io.StringIO()
|
|
46
|
+
|
|
47
|
+
class Product(BaseDBModel):
|
|
48
|
+
name: str
|
|
49
|
+
price: float
|
|
50
|
+
|
|
51
|
+
db = SqliterDB(memory=True)
|
|
52
|
+
db.create_table(Product)
|
|
53
|
+
|
|
54
|
+
db.insert(Product(name="Item A", price=10.0))
|
|
55
|
+
db.insert(Product(name="Item B", price=20.0))
|
|
56
|
+
db.insert(Product(name="Item C", price=30.0))
|
|
57
|
+
|
|
58
|
+
# Greater than
|
|
59
|
+
expensive = db.select(Product).filter(price__gt=15.0).fetch_all()
|
|
60
|
+
output.write(f"Products > $15: {len(expensive)}\n")
|
|
61
|
+
|
|
62
|
+
# Less than or equal
|
|
63
|
+
cheap = db.select(Product).filter(price__lte=20.0).fetch_all()
|
|
64
|
+
output.write(f"Products <= $20: {len(cheap)}\n")
|
|
65
|
+
|
|
66
|
+
db.close()
|
|
67
|
+
return output.getvalue()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _run_in_operator() -> str:
|
|
71
|
+
"""Filter records matching any value in a list.
|
|
72
|
+
|
|
73
|
+
Use __in to match records against multiple possible values.
|
|
74
|
+
"""
|
|
75
|
+
output = io.StringIO()
|
|
76
|
+
|
|
77
|
+
class Task(BaseDBModel):
|
|
78
|
+
title: str
|
|
79
|
+
status: str
|
|
80
|
+
|
|
81
|
+
db = SqliterDB(memory=True)
|
|
82
|
+
db.create_table(Task)
|
|
83
|
+
|
|
84
|
+
db.insert(Task(title="Task 1", status="todo"))
|
|
85
|
+
db.insert(Task(title="Task 2", status="done"))
|
|
86
|
+
db.insert(Task(title="Task 3", status="in_progress"))
|
|
87
|
+
db.insert(Task(title="Task 4", status="done"))
|
|
88
|
+
|
|
89
|
+
results = (
|
|
90
|
+
db.select(Task).filter(status__in=["todo", "in_progress"]).fetch_all()
|
|
91
|
+
)
|
|
92
|
+
output.write(f"Active tasks: {len(results)}\n")
|
|
93
|
+
for task in results:
|
|
94
|
+
output.write(f" - {task.title}: {task.status}\n")
|
|
95
|
+
|
|
96
|
+
db.close()
|
|
97
|
+
return output.getvalue()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _run_like_operator() -> str:
|
|
101
|
+
"""Filter strings using SQL LIKE pattern matching.
|
|
102
|
+
|
|
103
|
+
Use __like with % wildcards for flexible string matching.
|
|
104
|
+
"""
|
|
105
|
+
output = io.StringIO()
|
|
106
|
+
|
|
107
|
+
class File(BaseDBModel):
|
|
108
|
+
name: str
|
|
109
|
+
|
|
110
|
+
db = SqliterDB(memory=True)
|
|
111
|
+
db.create_table(File)
|
|
112
|
+
|
|
113
|
+
db.insert(File(name="document.txt"))
|
|
114
|
+
db.insert(File(name="image.png"))
|
|
115
|
+
db.insert(File(name="data.csv"))
|
|
116
|
+
db.insert(File(name="notes.txt"))
|
|
117
|
+
|
|
118
|
+
results = db.select(File).filter(name__like="%.txt").fetch_all()
|
|
119
|
+
output.write(f"Text files: {len(results)}\n")
|
|
120
|
+
for file in results:
|
|
121
|
+
output.write(f" - {file.name}\n")
|
|
122
|
+
|
|
123
|
+
db.close()
|
|
124
|
+
return output.getvalue()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _run_not_equals() -> str:
|
|
128
|
+
"""Filter records that don't match a specific value.
|
|
129
|
+
|
|
130
|
+
Use __ne to exclude records with the specified value.
|
|
131
|
+
"""
|
|
132
|
+
output = io.StringIO()
|
|
133
|
+
|
|
134
|
+
class Item(BaseDBModel):
|
|
135
|
+
name: str
|
|
136
|
+
status: str
|
|
137
|
+
|
|
138
|
+
db = SqliterDB(memory=True)
|
|
139
|
+
db.create_table(Item)
|
|
140
|
+
|
|
141
|
+
db.insert(Item(name="Item 1", status="active"))
|
|
142
|
+
db.insert(Item(name="Item 2", status="archived"))
|
|
143
|
+
db.insert(Item(name="Item 3", status="active"))
|
|
144
|
+
|
|
145
|
+
results = db.select(Item).filter(status__ne="archived").fetch_all()
|
|
146
|
+
output.write(f"Non-archived items: {len(results)}\n")
|
|
147
|
+
|
|
148
|
+
db.close()
|
|
149
|
+
return output.getvalue()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _run_multiple_filters() -> str:
|
|
153
|
+
"""Chain multiple filters for complex queries.
|
|
154
|
+
|
|
155
|
+
Combine multiple filter() calls to narrow results with AND logic.
|
|
156
|
+
"""
|
|
157
|
+
output = io.StringIO()
|
|
158
|
+
|
|
159
|
+
class User(BaseDBModel):
|
|
160
|
+
name: str
|
|
161
|
+
age: int
|
|
162
|
+
city: str
|
|
163
|
+
|
|
164
|
+
db = SqliterDB(memory=True)
|
|
165
|
+
db.create_table(User)
|
|
166
|
+
|
|
167
|
+
db.insert(User(name="Alice", age=30, city="NYC"))
|
|
168
|
+
db.insert(User(name="Bob", age=25, city="LA"))
|
|
169
|
+
db.insert(User(name="Charlie", age=30, city="NYC"))
|
|
170
|
+
|
|
171
|
+
results = (
|
|
172
|
+
db.select(User).filter(age__gte=30).filter(city__eq="NYC").fetch_all()
|
|
173
|
+
)
|
|
174
|
+
output.write(f"Users in NYC aged 30+: {len(results)}\n")
|
|
175
|
+
for user in results:
|
|
176
|
+
output.write(f" - {user.name}, {user.age}\n")
|
|
177
|
+
|
|
178
|
+
db.close()
|
|
179
|
+
return output.getvalue()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _run_range_filters() -> str:
|
|
183
|
+
"""Filter records within a specific value range.
|
|
184
|
+
|
|
185
|
+
Combine __gte and __lte to find records in a range.
|
|
186
|
+
"""
|
|
187
|
+
output = io.StringIO()
|
|
188
|
+
|
|
189
|
+
class Product(BaseDBModel):
|
|
190
|
+
name: str
|
|
191
|
+
price: float
|
|
192
|
+
|
|
193
|
+
db = SqliterDB(memory=True)
|
|
194
|
+
db.create_table(Product)
|
|
195
|
+
|
|
196
|
+
for i in range(1, 11):
|
|
197
|
+
db.insert(Product(name=f"Product {i}", price=float(i * 10)))
|
|
198
|
+
|
|
199
|
+
results = (
|
|
200
|
+
db.select(Product)
|
|
201
|
+
.filter(price__gte=30.0)
|
|
202
|
+
.filter(price__lte=70.0)
|
|
203
|
+
.fetch_all()
|
|
204
|
+
)
|
|
205
|
+
output.write(f"Products $30-$70: {len(results)}\n")
|
|
206
|
+
|
|
207
|
+
db.close()
|
|
208
|
+
return output.getvalue()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _run_combined_operators() -> str:
|
|
212
|
+
"""Combine different filter types for precise queries.
|
|
213
|
+
|
|
214
|
+
Mix equality, comparison, and other operators in a single query.
|
|
215
|
+
"""
|
|
216
|
+
output = io.StringIO()
|
|
217
|
+
|
|
218
|
+
class Order(BaseDBModel):
|
|
219
|
+
id: str
|
|
220
|
+
amount: float
|
|
221
|
+
status: str
|
|
222
|
+
|
|
223
|
+
db = SqliterDB(memory=True)
|
|
224
|
+
db.create_table(Order)
|
|
225
|
+
|
|
226
|
+
db.insert(Order(id="001", amount=100.0, status="pending"))
|
|
227
|
+
db.insert(Order(id="002", amount=250.0, status="completed"))
|
|
228
|
+
db.insert(Order(id="003", amount=50.0, status="pending"))
|
|
229
|
+
db.insert(Order(id="004", amount=300.0, status="completed"))
|
|
230
|
+
|
|
231
|
+
results = (
|
|
232
|
+
db.select(Order)
|
|
233
|
+
.filter(status__eq="pending")
|
|
234
|
+
.filter(amount__gt=50.0)
|
|
235
|
+
.fetch_all()
|
|
236
|
+
)
|
|
237
|
+
output.write(f"Pending orders > $50: {len(results)}\n")
|
|
238
|
+
|
|
239
|
+
db.close()
|
|
240
|
+
return output.getvalue()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _run_isnull() -> str:
|
|
244
|
+
"""Find records with null (empty) field values.
|
|
245
|
+
|
|
246
|
+
Use __isnull=True to find records where a field is None.
|
|
247
|
+
"""
|
|
248
|
+
output = io.StringIO()
|
|
249
|
+
|
|
250
|
+
class Task(BaseDBModel):
|
|
251
|
+
title: str
|
|
252
|
+
assigned_to: Optional[str] = None
|
|
253
|
+
|
|
254
|
+
db = SqliterDB(memory=True)
|
|
255
|
+
db.create_table(Task)
|
|
256
|
+
|
|
257
|
+
db.insert(Task(title="Task 1", assigned_to="Alice"))
|
|
258
|
+
db.insert(Task(title="Task 2", assigned_to=None)) # Unassigned
|
|
259
|
+
db.insert(Task(title="Task 3", assigned_to="Bob"))
|
|
260
|
+
db.insert(Task(title="Task 4", assigned_to=None)) # Unassigned
|
|
261
|
+
|
|
262
|
+
# Find unassigned tasks
|
|
263
|
+
unassigned = db.select(Task).filter(assigned_to__isnull=True).fetch_all()
|
|
264
|
+
output.write(f"Unassigned tasks: {len(unassigned)}\n")
|
|
265
|
+
for task in unassigned:
|
|
266
|
+
output.write(f" - {task.title}\n")
|
|
267
|
+
|
|
268
|
+
db.close()
|
|
269
|
+
return output.getvalue()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _run_notnull() -> str:
|
|
273
|
+
"""Find records without null (empty) field values.
|
|
274
|
+
|
|
275
|
+
Use __notnull=True to find records where a field has a value.
|
|
276
|
+
"""
|
|
277
|
+
output = io.StringIO()
|
|
278
|
+
|
|
279
|
+
class Task(BaseDBModel):
|
|
280
|
+
title: str
|
|
281
|
+
assigned_to: Optional[str] = None
|
|
282
|
+
|
|
283
|
+
db = SqliterDB(memory=True)
|
|
284
|
+
db.create_table(Task)
|
|
285
|
+
|
|
286
|
+
db.insert(Task(title="Task 1", assigned_to="Alice"))
|
|
287
|
+
db.insert(Task(title="Task 2", assigned_to=None))
|
|
288
|
+
db.insert(Task(title="Task 3", assigned_to="Bob"))
|
|
289
|
+
db.insert(Task(title="Task 4", assigned_to=None))
|
|
290
|
+
|
|
291
|
+
# Find assigned tasks
|
|
292
|
+
assigned = db.select(Task).filter(assigned_to__notnull=True).fetch_all()
|
|
293
|
+
output.write(f"Assigned tasks: {len(assigned)}\n")
|
|
294
|
+
for task in assigned:
|
|
295
|
+
output.write(f" - {task.title}: {task.assigned_to}\n")
|
|
296
|
+
|
|
297
|
+
db.close()
|
|
298
|
+
return output.getvalue()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_category() -> DemoCategory:
|
|
302
|
+
"""Get the Query Filters demo category."""
|
|
303
|
+
return DemoCategory(
|
|
304
|
+
id="filters",
|
|
305
|
+
title="Query Filters",
|
|
306
|
+
icon="",
|
|
307
|
+
demos=[
|
|
308
|
+
Demo(
|
|
309
|
+
id="filter_eq",
|
|
310
|
+
title="Equals (__eq)",
|
|
311
|
+
description="Exact match filter",
|
|
312
|
+
category="filters",
|
|
313
|
+
code=extract_demo_code(_run_equals),
|
|
314
|
+
execute=_run_equals,
|
|
315
|
+
),
|
|
316
|
+
Demo(
|
|
317
|
+
id="filter_comparison",
|
|
318
|
+
title="Comparison Operators",
|
|
319
|
+
description="__gt, __lt, __gte, __lte (less/greater than)",
|
|
320
|
+
category="filters",
|
|
321
|
+
code=extract_demo_code(_run_comparison),
|
|
322
|
+
execute=_run_comparison,
|
|
323
|
+
),
|
|
324
|
+
Demo(
|
|
325
|
+
id="filter_in",
|
|
326
|
+
title="IN Operator (__in)",
|
|
327
|
+
description="Match against list of values",
|
|
328
|
+
category="filters",
|
|
329
|
+
code=extract_demo_code(_run_in_operator),
|
|
330
|
+
execute=_run_in_operator,
|
|
331
|
+
),
|
|
332
|
+
Demo(
|
|
333
|
+
id="filter_like",
|
|
334
|
+
title="LIKE Operator (__like)",
|
|
335
|
+
description="Pattern matching with wildcards",
|
|
336
|
+
category="filters",
|
|
337
|
+
code=extract_demo_code(_run_like_operator),
|
|
338
|
+
execute=_run_like_operator,
|
|
339
|
+
),
|
|
340
|
+
Demo(
|
|
341
|
+
id="filter_ne",
|
|
342
|
+
title="Not Equals (__ne)",
|
|
343
|
+
description="Exclude specific values",
|
|
344
|
+
category="filters",
|
|
345
|
+
code=extract_demo_code(_run_not_equals),
|
|
346
|
+
execute=_run_not_equals,
|
|
347
|
+
),
|
|
348
|
+
Demo(
|
|
349
|
+
id="filter_multiple",
|
|
350
|
+
title="Multiple Filters",
|
|
351
|
+
description="Chain filters for AND logic",
|
|
352
|
+
category="filters",
|
|
353
|
+
code=extract_demo_code(_run_multiple_filters),
|
|
354
|
+
execute=_run_multiple_filters,
|
|
355
|
+
),
|
|
356
|
+
Demo(
|
|
357
|
+
id="filter_range",
|
|
358
|
+
title="Range Queries",
|
|
359
|
+
description="Query within a value range",
|
|
360
|
+
category="filters",
|
|
361
|
+
code=extract_demo_code(_run_range_filters),
|
|
362
|
+
execute=_run_range_filters,
|
|
363
|
+
),
|
|
364
|
+
Demo(
|
|
365
|
+
id="filter_combined",
|
|
366
|
+
title="Combined Operators",
|
|
367
|
+
description="Multiple filter types together",
|
|
368
|
+
category="filters",
|
|
369
|
+
code=extract_demo_code(_run_combined_operators),
|
|
370
|
+
execute=_run_combined_operators,
|
|
371
|
+
),
|
|
372
|
+
Demo(
|
|
373
|
+
id="filter_isnull",
|
|
374
|
+
title="IS NULL (__isnull)",
|
|
375
|
+
description="Find records with null values",
|
|
376
|
+
category="filters",
|
|
377
|
+
code=extract_demo_code(_run_isnull),
|
|
378
|
+
execute=_run_isnull,
|
|
379
|
+
),
|
|
380
|
+
Demo(
|
|
381
|
+
id="filter_notnull",
|
|
382
|
+
title="IS NOT NULL (__notnull)",
|
|
383
|
+
description="Find records without null values",
|
|
384
|
+
category="filters",
|
|
385
|
+
code=extract_demo_code(_run_notnull),
|
|
386
|
+
execute=_run_notnull,
|
|
387
|
+
),
|
|
388
|
+
],
|
|
389
|
+
)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Models & Tables demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Optional, Union
|
|
8
|
+
|
|
9
|
+
from sqliter import SqliterDB
|
|
10
|
+
from sqliter.model import BaseDBModel
|
|
11
|
+
from sqliter.tui.demos.base import Demo, DemoCategory, extract_demo_code
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _run_basic_model() -> str:
|
|
15
|
+
"""Define a simple model with automatic primary key generation.
|
|
16
|
+
|
|
17
|
+
BaseDBModel provides an auto-incrementing 'pk' field and handles
|
|
18
|
+
all the database table creation.
|
|
19
|
+
"""
|
|
20
|
+
output = io.StringIO()
|
|
21
|
+
|
|
22
|
+
class User(BaseDBModel):
|
|
23
|
+
name: str
|
|
24
|
+
age: int
|
|
25
|
+
email: str
|
|
26
|
+
|
|
27
|
+
db = SqliterDB(memory=True)
|
|
28
|
+
db.create_table(User)
|
|
29
|
+
|
|
30
|
+
user = db.insert(User(name="Alice", age=30, email="alice@example.com"))
|
|
31
|
+
output.write(f"Created user: {user.name}\n")
|
|
32
|
+
output.write(f"Primary key: {user.pk}\n")
|
|
33
|
+
output.write(f"Age: {user.age}\n")
|
|
34
|
+
output.write(f"Email: {user.email}\n")
|
|
35
|
+
|
|
36
|
+
db.close()
|
|
37
|
+
return output.getvalue()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_custom_table_name() -> str:
|
|
41
|
+
"""Override the auto-generated table name.
|
|
42
|
+
|
|
43
|
+
Set __tablename__ to use a custom table name instead of the
|
|
44
|
+
auto-pluralized model class name.
|
|
45
|
+
"""
|
|
46
|
+
output = io.StringIO()
|
|
47
|
+
|
|
48
|
+
class Person(BaseDBModel):
|
|
49
|
+
"""Person model with custom table name."""
|
|
50
|
+
|
|
51
|
+
__tablename__ = "people"
|
|
52
|
+
name: str
|
|
53
|
+
|
|
54
|
+
db = SqliterDB(memory=True)
|
|
55
|
+
db.create_table(Person)
|
|
56
|
+
|
|
57
|
+
output.write(f"Table created: {Person.__tablename__}\n")
|
|
58
|
+
output.write("The model uses 'people' instead of 'persons'\n")
|
|
59
|
+
|
|
60
|
+
person = db.insert(Person(name="Bob"))
|
|
61
|
+
output.write(f"Inserted: {person.name} (pk={person.pk})\n")
|
|
62
|
+
|
|
63
|
+
db.close()
|
|
64
|
+
return output.getvalue()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _run_field_types() -> str:
|
|
68
|
+
"""Use various field types in your models.
|
|
69
|
+
|
|
70
|
+
BaseDBModel supports str, int, float, bool, and more with automatic
|
|
71
|
+
type conversion and validation.
|
|
72
|
+
"""
|
|
73
|
+
output = io.StringIO()
|
|
74
|
+
|
|
75
|
+
class Product(BaseDBModel):
|
|
76
|
+
name: str
|
|
77
|
+
price: float
|
|
78
|
+
in_stock: bool
|
|
79
|
+
quantity: int
|
|
80
|
+
created_at: int
|
|
81
|
+
|
|
82
|
+
db = SqliterDB(memory=True)
|
|
83
|
+
db.create_table(Product)
|
|
84
|
+
|
|
85
|
+
product = db.insert(
|
|
86
|
+
Product(
|
|
87
|
+
name="Widget",
|
|
88
|
+
price=19.99,
|
|
89
|
+
in_stock=True,
|
|
90
|
+
quantity=100,
|
|
91
|
+
created_at=int(datetime.now(timezone.utc).timestamp()),
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
output.write(f"Product: {product.name}\n")
|
|
95
|
+
output.write(f"Price: ${product.price}\n")
|
|
96
|
+
output.write(f"In stock: {product.in_stock}\n")
|
|
97
|
+
output.write(f"Quantity: {product.quantity}\n")
|
|
98
|
+
output.write(f"Created: {product.created_at}\n")
|
|
99
|
+
|
|
100
|
+
db.close()
|
|
101
|
+
return output.getvalue()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _run_optional_fields() -> str:
|
|
105
|
+
"""Define fields that can be NULL (optional) in the database.
|
|
106
|
+
|
|
107
|
+
Use Optional[T] or Union[T, None] for nullable fields, with
|
|
108
|
+
optional default values.
|
|
109
|
+
"""
|
|
110
|
+
output = io.StringIO()
|
|
111
|
+
|
|
112
|
+
class Article(BaseDBModel):
|
|
113
|
+
title: str
|
|
114
|
+
content: Optional[str]
|
|
115
|
+
author: Optional[str] = "Anonymous"
|
|
116
|
+
|
|
117
|
+
db = SqliterDB(memory=True)
|
|
118
|
+
db.create_table(Article)
|
|
119
|
+
|
|
120
|
+
article1 = db.insert(Article(title="First Post", content=None))
|
|
121
|
+
output.write(f"Article 1: {article1.title}\n")
|
|
122
|
+
output.write(f"Content: {article1.content}\n")
|
|
123
|
+
output.write(f"Author: {article1.author}\n")
|
|
124
|
+
|
|
125
|
+
article2 = db.insert(
|
|
126
|
+
Article(title="Second Post", content="Hello world!", author="Bob"),
|
|
127
|
+
)
|
|
128
|
+
output.write(f"\nArticle 2: {article2.title}\n")
|
|
129
|
+
output.write(f"Content: {article2.content}\n")
|
|
130
|
+
output.write(f"Author: {article2.author}\n")
|
|
131
|
+
|
|
132
|
+
db.close()
|
|
133
|
+
return output.getvalue()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _run_default_values() -> str:
|
|
137
|
+
"""Set default values for fields.
|
|
138
|
+
|
|
139
|
+
Assign default values in the model definition to use when
|
|
140
|
+
inserting records without specifying those fields.
|
|
141
|
+
"""
|
|
142
|
+
output = io.StringIO()
|
|
143
|
+
|
|
144
|
+
class Task(BaseDBModel):
|
|
145
|
+
title: str
|
|
146
|
+
completed: bool = False
|
|
147
|
+
priority: int = 1
|
|
148
|
+
|
|
149
|
+
db = SqliterDB(memory=True)
|
|
150
|
+
db.create_table(Task)
|
|
151
|
+
|
|
152
|
+
task = db.insert(Task(title="New task"))
|
|
153
|
+
output.write(f"Task: {task.title}\n")
|
|
154
|
+
output.write(f"Completed: {task.completed} (default)\n")
|
|
155
|
+
output.write(f"Priority: {task.priority} (default)\n")
|
|
156
|
+
|
|
157
|
+
db.close()
|
|
158
|
+
return output.getvalue()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _run_complex_types() -> str:
|
|
162
|
+
"""Store complex data types like lists and dicts.
|
|
163
|
+
|
|
164
|
+
BaseDBModel automatically serializes lists, dicts, sets, and tuples
|
|
165
|
+
to BLOBs for SQLite storage, and deserializes them back.
|
|
166
|
+
"""
|
|
167
|
+
output = io.StringIO()
|
|
168
|
+
|
|
169
|
+
class Document(BaseDBModel):
|
|
170
|
+
title: str
|
|
171
|
+
tags: list[str]
|
|
172
|
+
metadata: dict[str, Union[str, int]]
|
|
173
|
+
|
|
174
|
+
db = SqliterDB(memory=True)
|
|
175
|
+
db.create_table(Document)
|
|
176
|
+
|
|
177
|
+
doc = db.insert(
|
|
178
|
+
Document(
|
|
179
|
+
title="Guide",
|
|
180
|
+
tags=["python", "database", "tutorial"],
|
|
181
|
+
metadata={"views": 1000, "rating": 4},
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
output.write(f"Document: {doc.title}\n")
|
|
185
|
+
output.write(f"Tags: {doc.tags}\n")
|
|
186
|
+
output.write(f"Metadata: {doc.metadata}\n")
|
|
187
|
+
|
|
188
|
+
db.close()
|
|
189
|
+
return output.getvalue()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_category() -> DemoCategory:
|
|
193
|
+
"""Get the Models & Tables demo category."""
|
|
194
|
+
return DemoCategory(
|
|
195
|
+
id="models",
|
|
196
|
+
title="Models & Tables",
|
|
197
|
+
icon="",
|
|
198
|
+
demos=[
|
|
199
|
+
Demo(
|
|
200
|
+
id="model_basic",
|
|
201
|
+
title="Basic Model",
|
|
202
|
+
description="Define a simple model with fields",
|
|
203
|
+
category="models",
|
|
204
|
+
code=extract_demo_code(_run_basic_model),
|
|
205
|
+
execute=_run_basic_model,
|
|
206
|
+
),
|
|
207
|
+
Demo(
|
|
208
|
+
id="model_custom_table",
|
|
209
|
+
title="Custom Table Name",
|
|
210
|
+
description="Specify a custom table name",
|
|
211
|
+
category="models",
|
|
212
|
+
code=extract_demo_code(_run_custom_table_name),
|
|
213
|
+
execute=_run_custom_table_name,
|
|
214
|
+
),
|
|
215
|
+
Demo(
|
|
216
|
+
id="model_field_types",
|
|
217
|
+
title="Field Types",
|
|
218
|
+
description="Various field type examples",
|
|
219
|
+
category="models",
|
|
220
|
+
code=extract_demo_code(_run_field_types),
|
|
221
|
+
execute=_run_field_types,
|
|
222
|
+
),
|
|
223
|
+
Demo(
|
|
224
|
+
id="model_optional",
|
|
225
|
+
title="Optional Fields",
|
|
226
|
+
description="Fields with None values and defaults",
|
|
227
|
+
category="models",
|
|
228
|
+
code=extract_demo_code(_run_optional_fields),
|
|
229
|
+
execute=_run_optional_fields,
|
|
230
|
+
),
|
|
231
|
+
Demo(
|
|
232
|
+
id="model_defaults",
|
|
233
|
+
title="Default Values",
|
|
234
|
+
description="Fields with default values",
|
|
235
|
+
category="models",
|
|
236
|
+
code=extract_demo_code(_run_default_values),
|
|
237
|
+
execute=_run_default_values,
|
|
238
|
+
),
|
|
239
|
+
Demo(
|
|
240
|
+
id="model_complex",
|
|
241
|
+
title="Complex Types",
|
|
242
|
+
description="Lists and dicts (stored as BLOBs)",
|
|
243
|
+
category="models",
|
|
244
|
+
code=extract_demo_code(_run_complex_types),
|
|
245
|
+
execute=_run_complex_types,
|
|
246
|
+
),
|
|
247
|
+
],
|
|
248
|
+
)
|