sqliter-py 0.12.0__py3-none-any.whl → 0.17.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 +29 -0
- sqliter/helpers.py +27 -0
- sqliter/model/model.py +21 -4
- sqliter/orm/__init__.py +17 -0
- sqliter/orm/fields.py +412 -0
- sqliter/orm/foreign_key.py +8 -0
- sqliter/orm/m2m.py +784 -0
- sqliter/orm/model.py +308 -0
- sqliter/orm/query.py +221 -0
- sqliter/orm/registry.py +440 -0
- sqliter/query/query.py +573 -51
- sqliter/sqliter.py +182 -47
- 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 +537 -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.12.0.dist-info → sqliter_py-0.17.0.dist-info}/METADATA +28 -14
- sqliter_py-0.17.0.dist-info/RECORD +48 -0
- {sqliter_py-0.12.0.dist-info → sqliter_py-0.17.0.dist-info}/WHEEL +2 -2
- sqliter_py-0.17.0.dist-info/entry_points.txt +3 -0
- sqliter_py-0.12.0.dist-info/RECORD +0 -15
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Query Results & Aggregation demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
|
|
7
|
+
from sqliter import SqliterDB
|
|
8
|
+
from sqliter.model import BaseDBModel
|
|
9
|
+
from sqliter.tui.demos.base import Demo, DemoCategory, extract_demo_code
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _run_fetch_all() -> str:
|
|
13
|
+
"""Fetch all records matching a query.
|
|
14
|
+
|
|
15
|
+
Use fetch_all() to get a list of all matching records.
|
|
16
|
+
"""
|
|
17
|
+
output = io.StringIO()
|
|
18
|
+
|
|
19
|
+
class User(BaseDBModel):
|
|
20
|
+
name: str
|
|
21
|
+
age: int
|
|
22
|
+
|
|
23
|
+
db = SqliterDB(memory=True)
|
|
24
|
+
db.create_table(User)
|
|
25
|
+
|
|
26
|
+
for i in range(5):
|
|
27
|
+
db.insert(User(name=f"User {i}", age=20 + i))
|
|
28
|
+
|
|
29
|
+
results = db.select(User).fetch_all()
|
|
30
|
+
output.write(f"Total users: {len(results)}\n")
|
|
31
|
+
for user in results:
|
|
32
|
+
output.write(f" - {user.name}, age {user.age}\n")
|
|
33
|
+
|
|
34
|
+
db.close()
|
|
35
|
+
return output.getvalue()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _run_fetch_one() -> str:
|
|
39
|
+
"""Fetch a single record or return None if not found.
|
|
40
|
+
|
|
41
|
+
Use fetch_one() to get one matching record, returning None if
|
|
42
|
+
no records match the query.
|
|
43
|
+
"""
|
|
44
|
+
output = io.StringIO()
|
|
45
|
+
|
|
46
|
+
class Task(BaseDBModel):
|
|
47
|
+
title: str
|
|
48
|
+
priority: int
|
|
49
|
+
|
|
50
|
+
db = SqliterDB(memory=True)
|
|
51
|
+
db.create_table(Task)
|
|
52
|
+
|
|
53
|
+
db.insert(Task(title="High priority", priority=1))
|
|
54
|
+
db.insert(Task(title="Medium priority", priority=2))
|
|
55
|
+
db.insert(Task(title="Low priority", priority=3))
|
|
56
|
+
|
|
57
|
+
task = db.select(Task).filter(priority__eq=1).fetch_one()
|
|
58
|
+
if task is not None:
|
|
59
|
+
output.write(f"Single result: {task.title}\n")
|
|
60
|
+
|
|
61
|
+
# Also test no results case
|
|
62
|
+
no_task = db.select(Task).filter(priority__eq=999).fetch_one()
|
|
63
|
+
if no_task is None:
|
|
64
|
+
output.write("No task found with priority 999\n")
|
|
65
|
+
|
|
66
|
+
db.close()
|
|
67
|
+
return output.getvalue()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _run_fetch_first_last() -> str:
|
|
71
|
+
"""Fetch the first or last record from results.
|
|
72
|
+
|
|
73
|
+
Use fetch_first() or fetch_last() to get a single record
|
|
74
|
+
from the beginning or end of the result set.
|
|
75
|
+
"""
|
|
76
|
+
output = io.StringIO()
|
|
77
|
+
|
|
78
|
+
class Item(BaseDBModel):
|
|
79
|
+
name: str
|
|
80
|
+
|
|
81
|
+
db = SqliterDB(memory=True)
|
|
82
|
+
db.create_table(Item)
|
|
83
|
+
|
|
84
|
+
for name in ["Alpha", "Beta", "Gamma", "Delta"]:
|
|
85
|
+
db.insert(Item(name=name))
|
|
86
|
+
|
|
87
|
+
first = db.select(Item).fetch_first()
|
|
88
|
+
if first is not None:
|
|
89
|
+
output.write(f"First: {first.name}\n")
|
|
90
|
+
|
|
91
|
+
last = db.select(Item).fetch_last()
|
|
92
|
+
if last is not None:
|
|
93
|
+
output.write(f"Last: {last.name}\n")
|
|
94
|
+
|
|
95
|
+
db.close()
|
|
96
|
+
return output.getvalue()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _run_count() -> str:
|
|
100
|
+
"""Count the number of matching records.
|
|
101
|
+
|
|
102
|
+
Use count() to efficiently count records without fetching them.
|
|
103
|
+
"""
|
|
104
|
+
output = io.StringIO()
|
|
105
|
+
|
|
106
|
+
class Product(BaseDBModel):
|
|
107
|
+
name: str
|
|
108
|
+
category: str
|
|
109
|
+
|
|
110
|
+
db = SqliterDB(memory=True)
|
|
111
|
+
db.create_table(Product)
|
|
112
|
+
|
|
113
|
+
db.insert(Product(name="Laptop", category="electronics"))
|
|
114
|
+
db.insert(Product(name="Phone", category="electronics"))
|
|
115
|
+
db.insert(Product(name="Desk", category="furniture"))
|
|
116
|
+
|
|
117
|
+
total = db.select(Product).count()
|
|
118
|
+
output.write(f"Total products: {total}\n")
|
|
119
|
+
|
|
120
|
+
electronics = db.select(Product).filter(category__eq="electronics").count()
|
|
121
|
+
output.write(f"Electronics: {electronics}\n")
|
|
122
|
+
|
|
123
|
+
db.close()
|
|
124
|
+
return output.getvalue()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _run_exists() -> str:
|
|
128
|
+
"""Check if any records match the query.
|
|
129
|
+
|
|
130
|
+
Use exists() to efficiently check for matching records without
|
|
131
|
+
fetching them - returns True/False.
|
|
132
|
+
"""
|
|
133
|
+
output = io.StringIO()
|
|
134
|
+
|
|
135
|
+
class User(BaseDBModel):
|
|
136
|
+
username: str
|
|
137
|
+
|
|
138
|
+
db = SqliterDB(memory=True)
|
|
139
|
+
db.create_table(User)
|
|
140
|
+
|
|
141
|
+
db.insert(User(username="alice"))
|
|
142
|
+
db.insert(User(username="bob"))
|
|
143
|
+
|
|
144
|
+
exists = db.select(User).filter(username__eq="alice").exists()
|
|
145
|
+
output.write(f"User 'alice' exists: {exists}\n")
|
|
146
|
+
|
|
147
|
+
not_exists = db.select(User).filter(username__eq="charlie").exists()
|
|
148
|
+
output.write(f"User 'charlie' exists: {not_exists}\n")
|
|
149
|
+
|
|
150
|
+
db.close()
|
|
151
|
+
return output.getvalue()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _run_aggregates() -> str:
|
|
155
|
+
"""Calculate aggregates using Python after fetching data.
|
|
156
|
+
|
|
157
|
+
SQLiter doesn't support SQL-level aggregates (GROUP BY, HAVING).
|
|
158
|
+
Use Python's sum(), len(), etc. after fetching results.
|
|
159
|
+
"""
|
|
160
|
+
output = io.StringIO()
|
|
161
|
+
|
|
162
|
+
class Sale(BaseDBModel):
|
|
163
|
+
amount: float
|
|
164
|
+
|
|
165
|
+
db = SqliterDB(memory=True)
|
|
166
|
+
db.create_table(Sale)
|
|
167
|
+
|
|
168
|
+
for amount in [10.0, 20.0, 30.0, 40.0, 50.0]:
|
|
169
|
+
db.insert(Sale(amount=amount))
|
|
170
|
+
|
|
171
|
+
# Note: SQLiter doesn't support SQL-level aggregates (GROUP BY, HAVING)
|
|
172
|
+
# Use Python for calculations after fetching data
|
|
173
|
+
results = db.select(Sale).fetch_all()
|
|
174
|
+
total = sum(s.amount for s in results)
|
|
175
|
+
average = total / len(results)
|
|
176
|
+
output.write(f"Total sales: ${total:.2f}\n")
|
|
177
|
+
output.write(f"Average sale: ${average:.2f}\n")
|
|
178
|
+
output.write(f"Count: {len(results)}\n")
|
|
179
|
+
output.write("\n(Aggregates calculated in Python, not SQL)\n")
|
|
180
|
+
|
|
181
|
+
db.close()
|
|
182
|
+
return output.getvalue()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def get_category() -> DemoCategory:
|
|
186
|
+
"""Get the Query Results demo category."""
|
|
187
|
+
return DemoCategory(
|
|
188
|
+
id="results",
|
|
189
|
+
title="Query Results",
|
|
190
|
+
icon="",
|
|
191
|
+
demos=[
|
|
192
|
+
Demo(
|
|
193
|
+
id="result_fetch_all",
|
|
194
|
+
title="Fetch All",
|
|
195
|
+
description="Get all matching records",
|
|
196
|
+
category="results",
|
|
197
|
+
code=extract_demo_code(_run_fetch_all),
|
|
198
|
+
execute=_run_fetch_all,
|
|
199
|
+
),
|
|
200
|
+
Demo(
|
|
201
|
+
id="result_fetch_one",
|
|
202
|
+
title="Fetch One",
|
|
203
|
+
description="Get single record or None",
|
|
204
|
+
category="results",
|
|
205
|
+
code=extract_demo_code(_run_fetch_one),
|
|
206
|
+
execute=_run_fetch_one,
|
|
207
|
+
),
|
|
208
|
+
Demo(
|
|
209
|
+
id="result_first_last",
|
|
210
|
+
title="Fetch First/Last",
|
|
211
|
+
description="Get first or last record",
|
|
212
|
+
category="results",
|
|
213
|
+
code=extract_demo_code(_run_fetch_first_last),
|
|
214
|
+
execute=_run_fetch_first_last,
|
|
215
|
+
),
|
|
216
|
+
Demo(
|
|
217
|
+
id="result_count",
|
|
218
|
+
title="Count",
|
|
219
|
+
description="Count matching records",
|
|
220
|
+
category="results",
|
|
221
|
+
code=extract_demo_code(_run_count),
|
|
222
|
+
execute=_run_count,
|
|
223
|
+
),
|
|
224
|
+
Demo(
|
|
225
|
+
id="result_exists",
|
|
226
|
+
title="Exists",
|
|
227
|
+
description="Check if any records match",
|
|
228
|
+
category="results",
|
|
229
|
+
code=extract_demo_code(_run_exists),
|
|
230
|
+
execute=_run_exists,
|
|
231
|
+
),
|
|
232
|
+
Demo(
|
|
233
|
+
id="result_aggregates",
|
|
234
|
+
title="Aggregates",
|
|
235
|
+
description="Calculate sum, average, etc.",
|
|
236
|
+
category="results",
|
|
237
|
+
code=extract_demo_code(_run_aggregates),
|
|
238
|
+
execute=_run_aggregates,
|
|
239
|
+
),
|
|
240
|
+
],
|
|
241
|
+
)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""String Filter demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
|
|
7
|
+
from sqliter import SqliterDB
|
|
8
|
+
from sqliter.model import BaseDBModel
|
|
9
|
+
from sqliter.tui.demos.base import Demo, DemoCategory, extract_demo_code
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _run_startswith() -> str:
|
|
13
|
+
"""Filter strings that start with a specific prefix.
|
|
14
|
+
|
|
15
|
+
Use __startswith to match records where a field begins with
|
|
16
|
+
the specified value.
|
|
17
|
+
"""
|
|
18
|
+
output = io.StringIO()
|
|
19
|
+
|
|
20
|
+
class User(BaseDBModel):
|
|
21
|
+
username: str
|
|
22
|
+
|
|
23
|
+
db = SqliterDB(memory=True)
|
|
24
|
+
db.create_table(User)
|
|
25
|
+
|
|
26
|
+
db.insert(User(username="alice_wonder"))
|
|
27
|
+
db.insert(User(username="alice_smith"))
|
|
28
|
+
db.insert(User(username="bob_builder"))
|
|
29
|
+
|
|
30
|
+
# Find usernames starting with "alice"
|
|
31
|
+
results = db.select(User).filter(username__startswith="alice").fetch_all()
|
|
32
|
+
output.write(f"Users starting with 'alice': {len(results)}\n")
|
|
33
|
+
for user in results:
|
|
34
|
+
output.write(f" - {user.username}\n")
|
|
35
|
+
|
|
36
|
+
db.close()
|
|
37
|
+
return output.getvalue()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_endswith() -> str:
|
|
41
|
+
"""Filter strings that end with a specific suffix.
|
|
42
|
+
|
|
43
|
+
Use __endswith to match records where a field ends with
|
|
44
|
+
the specified value.
|
|
45
|
+
"""
|
|
46
|
+
output = io.StringIO()
|
|
47
|
+
|
|
48
|
+
class File(BaseDBModel):
|
|
49
|
+
filename: str
|
|
50
|
+
|
|
51
|
+
db = SqliterDB(memory=True)
|
|
52
|
+
db.create_table(File)
|
|
53
|
+
|
|
54
|
+
db.insert(File(filename="document.txt"))
|
|
55
|
+
db.insert(File(filename="image.png"))
|
|
56
|
+
db.insert(File(filename="notes.txt"))
|
|
57
|
+
db.insert(File(filename="data.csv"))
|
|
58
|
+
|
|
59
|
+
# Find files ending with ".txt"
|
|
60
|
+
results = db.select(File).filter(filename__endswith=".txt").fetch_all()
|
|
61
|
+
output.write(f"Text files: {len(results)}\n")
|
|
62
|
+
for file in results:
|
|
63
|
+
output.write(f" - {file.filename}\n")
|
|
64
|
+
|
|
65
|
+
db.close()
|
|
66
|
+
return output.getvalue()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _run_contains() -> str:
|
|
70
|
+
"""Filter strings that contain a specific substring.
|
|
71
|
+
|
|
72
|
+
Use __contains to match records where a field contains
|
|
73
|
+
the specified value anywhere within it.
|
|
74
|
+
"""
|
|
75
|
+
output = io.StringIO()
|
|
76
|
+
|
|
77
|
+
class Product(BaseDBModel):
|
|
78
|
+
name: str
|
|
79
|
+
|
|
80
|
+
db = SqliterDB(memory=True)
|
|
81
|
+
db.create_table(Product)
|
|
82
|
+
|
|
83
|
+
db.insert(Product(name="Apple iPhone"))
|
|
84
|
+
db.insert(Product(name="Samsung Galaxy"))
|
|
85
|
+
db.insert(Product(name="Apple iPad"))
|
|
86
|
+
db.insert(Product(name="Google Pixel"))
|
|
87
|
+
|
|
88
|
+
# Find products containing "Apple"
|
|
89
|
+
results = db.select(Product).filter(name__contains="Apple").fetch_all()
|
|
90
|
+
output.write(f"Products containing 'Apple': {len(results)}\n")
|
|
91
|
+
for product in results:
|
|
92
|
+
output.write(f" - {product.name}\n")
|
|
93
|
+
|
|
94
|
+
db.close()
|
|
95
|
+
return output.getvalue()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _run_case_insensitive() -> str:
|
|
99
|
+
"""Filter strings ignoring case with __istartswith and __iendswith.
|
|
100
|
+
|
|
101
|
+
Use case-insensitive operators to match strings regardless of
|
|
102
|
+
capitalization.
|
|
103
|
+
"""
|
|
104
|
+
output = io.StringIO()
|
|
105
|
+
|
|
106
|
+
class User(BaseDBModel):
|
|
107
|
+
email: str
|
|
108
|
+
|
|
109
|
+
db = SqliterDB(memory=True)
|
|
110
|
+
db.create_table(User)
|
|
111
|
+
|
|
112
|
+
db.insert(User(email="ALICE@example.com"))
|
|
113
|
+
db.insert(User(email="bob@EXAMPLE.com"))
|
|
114
|
+
db.insert(User(email="charlie@test.com"))
|
|
115
|
+
|
|
116
|
+
# Find emails ending with "@example.com" (case-insensitive)
|
|
117
|
+
results = (
|
|
118
|
+
db.select(User).filter(email__iendswith="@example.com").fetch_all()
|
|
119
|
+
)
|
|
120
|
+
output.write(f"Emails ending with '@example.com': {len(results)}\n")
|
|
121
|
+
for user in results:
|
|
122
|
+
output.write(f" - {user.email}\n")
|
|
123
|
+
|
|
124
|
+
# Find emails starting with "BOB" (case-insensitive)
|
|
125
|
+
bob_results = db.select(User).filter(email__istartswith="BOB").fetch_all()
|
|
126
|
+
output.write(f"\nEmails starting with 'BOB': {len(bob_results)}\n")
|
|
127
|
+
for user in bob_results:
|
|
128
|
+
output.write(f" - {user.email}\n")
|
|
129
|
+
|
|
130
|
+
db.close()
|
|
131
|
+
return output.getvalue()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _run_icontains() -> str:
|
|
135
|
+
"""Filter strings containing a substring, ignoring case.
|
|
136
|
+
|
|
137
|
+
Use __icontains for case-insensitive substring matching.
|
|
138
|
+
"""
|
|
139
|
+
output = io.StringIO()
|
|
140
|
+
|
|
141
|
+
class Article(BaseDBModel):
|
|
142
|
+
title: str
|
|
143
|
+
|
|
144
|
+
db = SqliterDB(memory=True)
|
|
145
|
+
db.create_table(Article)
|
|
146
|
+
|
|
147
|
+
db.insert(Article(title="Python Programming Guide"))
|
|
148
|
+
db.insert(Article(title="Advanced PYTHON Techniques"))
|
|
149
|
+
db.insert(Article(title="Web Development"))
|
|
150
|
+
db.insert(Article(title="python for Beginners"))
|
|
151
|
+
|
|
152
|
+
# Find articles containing "python" (case-insensitive)
|
|
153
|
+
results = db.select(Article).filter(title__icontains="python").fetch_all()
|
|
154
|
+
output.write(f"Articles containing 'python': {len(results)}\n")
|
|
155
|
+
for article in results:
|
|
156
|
+
output.write(f" - {article.title}\n")
|
|
157
|
+
|
|
158
|
+
db.close()
|
|
159
|
+
return output.getvalue()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_category() -> DemoCategory:
|
|
163
|
+
"""Get the String Filters demo category."""
|
|
164
|
+
return DemoCategory(
|
|
165
|
+
id="string_filters",
|
|
166
|
+
title="String Filters",
|
|
167
|
+
icon="",
|
|
168
|
+
demos=[
|
|
169
|
+
Demo(
|
|
170
|
+
id="string_startswith",
|
|
171
|
+
title="Starts With (__startswith)",
|
|
172
|
+
description="Match strings starting with prefix",
|
|
173
|
+
category="string_filters",
|
|
174
|
+
code=extract_demo_code(_run_startswith),
|
|
175
|
+
execute=_run_startswith,
|
|
176
|
+
),
|
|
177
|
+
Demo(
|
|
178
|
+
id="string_endswith",
|
|
179
|
+
title="Ends With (__endswith)",
|
|
180
|
+
description="Match strings ending with suffix",
|
|
181
|
+
category="string_filters",
|
|
182
|
+
code=extract_demo_code(_run_endswith),
|
|
183
|
+
execute=_run_endswith,
|
|
184
|
+
),
|
|
185
|
+
Demo(
|
|
186
|
+
id="string_contains",
|
|
187
|
+
title="Contains (__contains)",
|
|
188
|
+
description="Match strings containing substring",
|
|
189
|
+
category="string_filters",
|
|
190
|
+
code=extract_demo_code(_run_contains),
|
|
191
|
+
execute=_run_contains,
|
|
192
|
+
),
|
|
193
|
+
Demo(
|
|
194
|
+
id="string_case_insensitive",
|
|
195
|
+
title="Case-Insensitive (__i*)",
|
|
196
|
+
description="Match strings ignoring case",
|
|
197
|
+
category="string_filters",
|
|
198
|
+
code=extract_demo_code(_run_case_insensitive),
|
|
199
|
+
execute=_run_case_insensitive,
|
|
200
|
+
),
|
|
201
|
+
Demo(
|
|
202
|
+
id="string_icontains",
|
|
203
|
+
title="Contains Case-Insensitive",
|
|
204
|
+
description="Match substring ignoring case",
|
|
205
|
+
category="string_filters",
|
|
206
|
+
code=extract_demo_code(_run_icontains),
|
|
207
|
+
execute=_run_icontains,
|
|
208
|
+
),
|
|
209
|
+
],
|
|
210
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Auto Timestamp demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import time
|
|
7
|
+
from datetime import datetime, timezone
|
|
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_created_at() -> str:
|
|
15
|
+
"""Automatically track when records are created.
|
|
16
|
+
|
|
17
|
+
The created_at field is set automatically when you insert a record.
|
|
18
|
+
"""
|
|
19
|
+
output = io.StringIO()
|
|
20
|
+
|
|
21
|
+
class Article(BaseDBModel):
|
|
22
|
+
title: str
|
|
23
|
+
|
|
24
|
+
db = SqliterDB(memory=True)
|
|
25
|
+
db.create_table(Article)
|
|
26
|
+
|
|
27
|
+
article1 = db.insert(Article(title="First Post"))
|
|
28
|
+
dt1 = datetime.fromtimestamp(article1.created_at, tz=timezone.utc)
|
|
29
|
+
formatted_dt1 = dt1.strftime("%Y-%m-%d %H:%M:%S")
|
|
30
|
+
output.write(f"Article: {article1.title}\n")
|
|
31
|
+
output.write(f"Created: {article1.created_at} ({formatted_dt1} UTC)\n")
|
|
32
|
+
|
|
33
|
+
time.sleep(0.1)
|
|
34
|
+
|
|
35
|
+
article2 = db.insert(Article(title="Second Post"))
|
|
36
|
+
dt2 = datetime.fromtimestamp(article2.created_at, tz=timezone.utc)
|
|
37
|
+
formatted_dt2 = dt2.strftime("%Y-%m-%d %H:%M:%S")
|
|
38
|
+
output.write(f"\nArticle: {article2.title}\n")
|
|
39
|
+
output.write(f"Created: {article2.created_at} ({formatted_dt2} UTC)\n")
|
|
40
|
+
|
|
41
|
+
db.close()
|
|
42
|
+
return output.getvalue()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _run_updated_at() -> str:
|
|
46
|
+
"""Automatically track when records are last modified.
|
|
47
|
+
|
|
48
|
+
The updated_at field changes automatically when you update a record.
|
|
49
|
+
"""
|
|
50
|
+
output = io.StringIO()
|
|
51
|
+
|
|
52
|
+
class Task(BaseDBModel):
|
|
53
|
+
title: str
|
|
54
|
+
done: bool = False
|
|
55
|
+
|
|
56
|
+
db = SqliterDB(memory=True)
|
|
57
|
+
db.create_table(Task)
|
|
58
|
+
|
|
59
|
+
task = db.insert(Task(title="Original Task"))
|
|
60
|
+
created_dt = datetime.fromtimestamp(task.created_at, tz=timezone.utc)
|
|
61
|
+
updated_dt = datetime.fromtimestamp(task.updated_at, tz=timezone.utc)
|
|
62
|
+
formatted_created_dt = created_dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
63
|
+
formatted_updated_dt = updated_dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
64
|
+
output.write(f"Task: {task.title}\n")
|
|
65
|
+
output.write(f"Created: {task.created_at} ({formatted_created_dt} UTC)\n")
|
|
66
|
+
output.write(f"Updated: {task.updated_at} ({formatted_updated_dt} UTC)\n")
|
|
67
|
+
|
|
68
|
+
# Sleep for 1 second to ensure different timestamps on fast machines
|
|
69
|
+
time.sleep(1)
|
|
70
|
+
|
|
71
|
+
task.title = "Updated Task"
|
|
72
|
+
task.done = True
|
|
73
|
+
db.update(task)
|
|
74
|
+
updated_task = task
|
|
75
|
+
updated_created_dt = datetime.fromtimestamp(
|
|
76
|
+
updated_task.created_at, tz=timezone.utc
|
|
77
|
+
)
|
|
78
|
+
updated_updated_dt = datetime.fromtimestamp(
|
|
79
|
+
updated_task.updated_at, tz=timezone.utc
|
|
80
|
+
)
|
|
81
|
+
formatted_updated_created_dt = updated_created_dt.strftime(
|
|
82
|
+
"%Y-%m-%d %H:%M:%S"
|
|
83
|
+
)
|
|
84
|
+
formatted_updated_updated_dt = updated_updated_dt.strftime(
|
|
85
|
+
"%Y-%m-%d %H:%M:%S"
|
|
86
|
+
)
|
|
87
|
+
output.write("\nAfter update:\n")
|
|
88
|
+
output.write(f"Title: {updated_task.title}\n")
|
|
89
|
+
output.write(
|
|
90
|
+
f"Created: {updated_task.created_at} "
|
|
91
|
+
f"({formatted_updated_created_dt} UTC)\n"
|
|
92
|
+
)
|
|
93
|
+
output.write(
|
|
94
|
+
f"Updated: {updated_task.updated_at} "
|
|
95
|
+
f"({formatted_updated_updated_dt} UTC)\n"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
db.close()
|
|
99
|
+
return output.getvalue()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_category() -> DemoCategory:
|
|
103
|
+
"""Get the Auto Timestamp demo category."""
|
|
104
|
+
return DemoCategory(
|
|
105
|
+
id="timestamps",
|
|
106
|
+
title="Auto Timestamps",
|
|
107
|
+
icon="",
|
|
108
|
+
demos=[
|
|
109
|
+
Demo(
|
|
110
|
+
id="timestamp_created",
|
|
111
|
+
title="Auto created_at",
|
|
112
|
+
description="Track when records are created",
|
|
113
|
+
category="timestamps",
|
|
114
|
+
code=extract_demo_code(_run_created_at),
|
|
115
|
+
execute=_run_created_at,
|
|
116
|
+
),
|
|
117
|
+
Demo(
|
|
118
|
+
id="timestamp_updated",
|
|
119
|
+
title="Auto updated_at",
|
|
120
|
+
description="Track when records are modified",
|
|
121
|
+
category="timestamps",
|
|
122
|
+
code=extract_demo_code(_run_updated_at),
|
|
123
|
+
execute=_run_updated_at,
|
|
124
|
+
),
|
|
125
|
+
],
|
|
126
|
+
)
|