sqliter-py 0.12.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 +13 -0
- sqliter/model/model.py +42 -3
- 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 +573 -51
- sqliter/sqliter.py +141 -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 +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.12.0.dist-info → sqliter_py-0.16.0.dist-info}/METADATA +23 -7
- sqliter_py-0.16.0.dist-info/RECORD +47 -0
- {sqliter_py-0.12.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.12.0.dist-info/RECORD +0 -15
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Unique & Foreign Key constraint demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from typing import Annotated, Optional
|
|
7
|
+
|
|
8
|
+
from sqliter import SqliterDB
|
|
9
|
+
from sqliter.model import BaseDBModel
|
|
10
|
+
from sqliter.model.unique import unique
|
|
11
|
+
from sqliter.orm.foreign_key import ForeignKey
|
|
12
|
+
from sqliter.tui.demos.base import Demo, DemoCategory, extract_demo_code
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _run_unique_field() -> str:
|
|
16
|
+
"""Enforce uniqueness on a field to prevent duplicate values.
|
|
17
|
+
|
|
18
|
+
Use unique() to ensure no two records have the same value for
|
|
19
|
+
a specific field (like email).
|
|
20
|
+
"""
|
|
21
|
+
output = io.StringIO()
|
|
22
|
+
|
|
23
|
+
class User(BaseDBModel):
|
|
24
|
+
email: Annotated[str, unique()]
|
|
25
|
+
name: str
|
|
26
|
+
|
|
27
|
+
db = SqliterDB(memory=True)
|
|
28
|
+
db.create_table(User)
|
|
29
|
+
|
|
30
|
+
user1 = db.insert(User(email="alice@example.com", name="Alice"))
|
|
31
|
+
output.write(f"Created: {user1.name} ({user1.email})\n")
|
|
32
|
+
|
|
33
|
+
user2 = db.insert(User(email="bob@example.com", name="Bob"))
|
|
34
|
+
output.write(f"Created: {user2.name} ({user2.email})\n")
|
|
35
|
+
|
|
36
|
+
db.close()
|
|
37
|
+
return output.getvalue()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_multi_field_unique() -> str:
|
|
41
|
+
"""Enforce uniqueness on multiple fields.
|
|
42
|
+
|
|
43
|
+
Each unique() field is constrained independently (not a composite
|
|
44
|
+
unique constraint).
|
|
45
|
+
"""
|
|
46
|
+
output = io.StringIO()
|
|
47
|
+
|
|
48
|
+
class Enrollment(BaseDBModel):
|
|
49
|
+
student_id: Annotated[int, unique()]
|
|
50
|
+
course_id: Annotated[int, unique()]
|
|
51
|
+
|
|
52
|
+
db = SqliterDB(memory=True)
|
|
53
|
+
db.create_table(Enrollment)
|
|
54
|
+
|
|
55
|
+
output.write("Table created with unique fields (each column independent)\n")
|
|
56
|
+
enrollment = db.insert(Enrollment(student_id=1, course_id=101))
|
|
57
|
+
output.write(
|
|
58
|
+
f"Enrolled student {enrollment.student_id} in course "
|
|
59
|
+
f"{enrollment.course_id}\n"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
db.close()
|
|
63
|
+
return output.getvalue()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _run_foreign_key_cascade() -> str:
|
|
67
|
+
"""Automatically delete related records when parent is deleted.
|
|
68
|
+
|
|
69
|
+
CASCADE on_delete means deleting a record also deletes all
|
|
70
|
+
records that reference it via foreign key.
|
|
71
|
+
"""
|
|
72
|
+
output = io.StringIO()
|
|
73
|
+
|
|
74
|
+
class Author(BaseDBModel):
|
|
75
|
+
name: str
|
|
76
|
+
|
|
77
|
+
class Book(BaseDBModel):
|
|
78
|
+
title: str
|
|
79
|
+
author_id: ForeignKey[Author] = ForeignKey(
|
|
80
|
+
Author,
|
|
81
|
+
on_delete="CASCADE",
|
|
82
|
+
on_update="CASCADE",
|
|
83
|
+
null=True,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
db = SqliterDB(memory=True)
|
|
87
|
+
db.create_table(Author)
|
|
88
|
+
db.create_table(Book)
|
|
89
|
+
|
|
90
|
+
author = db.insert(Author(name="Jane Austen"))
|
|
91
|
+
book = db.insert(Book(title="Pride and Prejudice", author_id=author.pk))
|
|
92
|
+
output.write(f"Book '{book.title}' linked to author {author.pk}\n")
|
|
93
|
+
output.write("Foreign key: CASCADE on delete/update\n")
|
|
94
|
+
|
|
95
|
+
db.close()
|
|
96
|
+
return output.getvalue()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _run_foreign_key_restrict() -> str:
|
|
100
|
+
"""Prevent deletion of records that are referenced by others.
|
|
101
|
+
|
|
102
|
+
RESTRICT on_delete prevents deleting a record if other records
|
|
103
|
+
reference it via foreign key.
|
|
104
|
+
"""
|
|
105
|
+
output = io.StringIO()
|
|
106
|
+
|
|
107
|
+
class Category(BaseDBModel):
|
|
108
|
+
name: str
|
|
109
|
+
|
|
110
|
+
class Product(BaseDBModel):
|
|
111
|
+
name: str
|
|
112
|
+
category_id: ForeignKey[Category] = ForeignKey(
|
|
113
|
+
Category, on_delete="RESTRICT"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
db = SqliterDB(memory=True)
|
|
117
|
+
db.create_table(Category)
|
|
118
|
+
db.create_table(Product)
|
|
119
|
+
|
|
120
|
+
category = db.insert(Category(name="Electronics"))
|
|
121
|
+
product = db.insert(Product(name="Laptop", category_id=category.pk))
|
|
122
|
+
output.write(f"Product '{product.name}' in category '{category.name}'\n")
|
|
123
|
+
output.write(
|
|
124
|
+
"Foreign key: RESTRICT prevents deletion of referenced records\n"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
db.close()
|
|
128
|
+
return output.getvalue()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _run_foreign_key_set_null() -> str:
|
|
132
|
+
"""Set foreign key to NULL when referenced record is deleted.
|
|
133
|
+
|
|
134
|
+
SET NULL on_delete sets the foreign key field to None when the
|
|
135
|
+
referenced record is deleted (requires nullable FK).
|
|
136
|
+
"""
|
|
137
|
+
output = io.StringIO()
|
|
138
|
+
|
|
139
|
+
class Department(BaseDBModel):
|
|
140
|
+
name: str
|
|
141
|
+
|
|
142
|
+
class Employee(BaseDBModel):
|
|
143
|
+
name: str
|
|
144
|
+
department_id: Optional[ForeignKey[Department]] = ForeignKey(
|
|
145
|
+
Department,
|
|
146
|
+
on_delete="SET NULL",
|
|
147
|
+
null=True,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
db = SqliterDB(memory=True)
|
|
151
|
+
db.create_table(Department)
|
|
152
|
+
db.create_table(Employee)
|
|
153
|
+
|
|
154
|
+
dept = db.insert(Department(name="Engineering"))
|
|
155
|
+
emp = db.insert(Employee(name="Alice", department_id=dept.pk))
|
|
156
|
+
output.write(f"Employee '{emp.name}' in department {emp.department_id}\n")
|
|
157
|
+
output.write("Foreign key: SET NULL on delete of referenced record\n")
|
|
158
|
+
|
|
159
|
+
db.close()
|
|
160
|
+
return output.getvalue()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_category() -> DemoCategory:
|
|
164
|
+
"""Get the Constraints demo category."""
|
|
165
|
+
return DemoCategory(
|
|
166
|
+
id="constraints",
|
|
167
|
+
title="Constraints",
|
|
168
|
+
icon="",
|
|
169
|
+
demos=[
|
|
170
|
+
Demo(
|
|
171
|
+
id="constraint_unique_field",
|
|
172
|
+
title="Unique Field",
|
|
173
|
+
description="Enforce uniqueness on a field",
|
|
174
|
+
category="constraints",
|
|
175
|
+
code=extract_demo_code(_run_unique_field),
|
|
176
|
+
execute=_run_unique_field,
|
|
177
|
+
),
|
|
178
|
+
Demo(
|
|
179
|
+
id="constraint_multi_unique",
|
|
180
|
+
title="Multiple Unique Fields",
|
|
181
|
+
description="Multiple unique fields in one table",
|
|
182
|
+
category="constraints",
|
|
183
|
+
code=extract_demo_code(_run_multi_field_unique),
|
|
184
|
+
execute=_run_multi_field_unique,
|
|
185
|
+
),
|
|
186
|
+
Demo(
|
|
187
|
+
id="constraint_fk_cascade",
|
|
188
|
+
title="Foreign Key CASCADE",
|
|
189
|
+
description="Cascade deletes to related records",
|
|
190
|
+
category="constraints",
|
|
191
|
+
code=extract_demo_code(_run_foreign_key_cascade),
|
|
192
|
+
execute=_run_foreign_key_cascade,
|
|
193
|
+
),
|
|
194
|
+
Demo(
|
|
195
|
+
id="constraint_fk_restrict",
|
|
196
|
+
title="Foreign Key RESTRICT",
|
|
197
|
+
description="Prevent deletion of referenced records",
|
|
198
|
+
category="constraints",
|
|
199
|
+
code=extract_demo_code(_run_foreign_key_restrict),
|
|
200
|
+
execute=_run_foreign_key_restrict,
|
|
201
|
+
),
|
|
202
|
+
Demo(
|
|
203
|
+
id="constraint_fk_set_null",
|
|
204
|
+
title="Foreign Key SET NULL",
|
|
205
|
+
description="Set field to NULL on reference deletion",
|
|
206
|
+
category="constraints",
|
|
207
|
+
code=extract_demo_code(_run_foreign_key_set_null),
|
|
208
|
+
execute=_run_foreign_key_set_null,
|
|
209
|
+
),
|
|
210
|
+
],
|
|
211
|
+
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""CRUD Operations 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_insert() -> str:
|
|
13
|
+
"""Insert new records into the database.
|
|
14
|
+
|
|
15
|
+
Use db.insert() to add new records, which returns the inserted
|
|
16
|
+
object with auto-generated primary key.
|
|
17
|
+
"""
|
|
18
|
+
output = io.StringIO()
|
|
19
|
+
|
|
20
|
+
class User(BaseDBModel):
|
|
21
|
+
name: str
|
|
22
|
+
email: str
|
|
23
|
+
|
|
24
|
+
db = SqliterDB(memory=True)
|
|
25
|
+
db.create_table(User)
|
|
26
|
+
|
|
27
|
+
user1 = db.insert(User(name="Alice", email="alice@example.com"))
|
|
28
|
+
output.write(f"Inserted: {user1.name} (pk={user1.pk})\n")
|
|
29
|
+
|
|
30
|
+
user2 = db.insert(User(name="Bob", email="bob@example.com"))
|
|
31
|
+
output.write(f"Inserted: {user2.name} (pk={user2.pk})\n")
|
|
32
|
+
|
|
33
|
+
db.close()
|
|
34
|
+
return output.getvalue()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _run_get_by_pk() -> str:
|
|
38
|
+
"""Retrieve a single record by primary key.
|
|
39
|
+
|
|
40
|
+
Use db.get(Model, pk) to fetch a specific record quickly.
|
|
41
|
+
"""
|
|
42
|
+
output = io.StringIO()
|
|
43
|
+
|
|
44
|
+
class Task(BaseDBModel):
|
|
45
|
+
title: str
|
|
46
|
+
done: bool = False
|
|
47
|
+
|
|
48
|
+
db = SqliterDB(memory=True)
|
|
49
|
+
db.create_table(Task)
|
|
50
|
+
|
|
51
|
+
task: Task = db.insert(Task(title="Buy groceries"))
|
|
52
|
+
output.write(f"Created: {task.title} (pk={task.pk})\n")
|
|
53
|
+
|
|
54
|
+
retrieved = db.get(Task, task.pk)
|
|
55
|
+
if retrieved is not None:
|
|
56
|
+
output.write(f"Retrieved: {retrieved.title}\n")
|
|
57
|
+
output.write(f"Same object: {retrieved.pk == task.pk}\n")
|
|
58
|
+
|
|
59
|
+
db.close()
|
|
60
|
+
return output.getvalue()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _run_update() -> str:
|
|
64
|
+
"""Update existing records in the database.
|
|
65
|
+
|
|
66
|
+
Modify field values and call db.update() to persist changes.
|
|
67
|
+
"""
|
|
68
|
+
output = io.StringIO()
|
|
69
|
+
|
|
70
|
+
class Item(BaseDBModel):
|
|
71
|
+
name: str
|
|
72
|
+
quantity: int
|
|
73
|
+
|
|
74
|
+
db = SqliterDB(memory=True)
|
|
75
|
+
db.create_table(Item)
|
|
76
|
+
|
|
77
|
+
item = db.insert(Item(name="Apples", quantity=5))
|
|
78
|
+
output.write(f"Created: {item.name} x{item.quantity}\n")
|
|
79
|
+
|
|
80
|
+
item.quantity = 10
|
|
81
|
+
db.update(item)
|
|
82
|
+
output.write(f"Updated: {item.name} x{item.quantity}\n")
|
|
83
|
+
|
|
84
|
+
db.close()
|
|
85
|
+
return output.getvalue()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _run_delete() -> str:
|
|
89
|
+
"""Delete records from the database.
|
|
90
|
+
|
|
91
|
+
Use db.delete(Model, pk) to permanently remove a record.
|
|
92
|
+
"""
|
|
93
|
+
output = io.StringIO()
|
|
94
|
+
|
|
95
|
+
class Note(BaseDBModel):
|
|
96
|
+
content: str
|
|
97
|
+
|
|
98
|
+
db = SqliterDB(memory=True)
|
|
99
|
+
db.create_table(Note)
|
|
100
|
+
|
|
101
|
+
note = db.insert(Note(content="Temporary note"))
|
|
102
|
+
output.write(f"Created note (pk={note.pk})\n")
|
|
103
|
+
|
|
104
|
+
db.delete(Note, note.pk)
|
|
105
|
+
output.write(f"Deleted note with pk={note.pk}\n")
|
|
106
|
+
|
|
107
|
+
all_notes = db.select(Note).fetch_all()
|
|
108
|
+
output.write(f"Remaining notes: {len(all_notes)}\n")
|
|
109
|
+
|
|
110
|
+
db.close()
|
|
111
|
+
return output.getvalue()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_category() -> DemoCategory:
|
|
115
|
+
"""Get the CRUD Operations demo category."""
|
|
116
|
+
return DemoCategory(
|
|
117
|
+
id="crud",
|
|
118
|
+
title="CRUD Operations",
|
|
119
|
+
icon="",
|
|
120
|
+
demos=[
|
|
121
|
+
Demo(
|
|
122
|
+
id="crud_insert",
|
|
123
|
+
title="Insert Records",
|
|
124
|
+
description="Create new records in the database",
|
|
125
|
+
category="crud",
|
|
126
|
+
code=extract_demo_code(_run_insert),
|
|
127
|
+
execute=_run_insert,
|
|
128
|
+
),
|
|
129
|
+
Demo(
|
|
130
|
+
id="crud_get",
|
|
131
|
+
title="Get by Primary Key",
|
|
132
|
+
description="Retrieve a record by its primary key",
|
|
133
|
+
category="crud",
|
|
134
|
+
code=extract_demo_code(_run_get_by_pk),
|
|
135
|
+
execute=_run_get_by_pk,
|
|
136
|
+
),
|
|
137
|
+
Demo(
|
|
138
|
+
id="crud_update",
|
|
139
|
+
title="Update Records",
|
|
140
|
+
description="Modify existing records",
|
|
141
|
+
category="crud",
|
|
142
|
+
code=extract_demo_code(_run_update),
|
|
143
|
+
execute=_run_update,
|
|
144
|
+
),
|
|
145
|
+
Demo(
|
|
146
|
+
id="crud_delete",
|
|
147
|
+
title="Delete Records",
|
|
148
|
+
description="Remove records from the database",
|
|
149
|
+
category="crud",
|
|
150
|
+
code=extract_demo_code(_run_delete),
|
|
151
|
+
execute=_run_delete,
|
|
152
|
+
),
|
|
153
|
+
],
|
|
154
|
+
)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Error Handling demos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
from pydantic import ValidationError
|
|
9
|
+
|
|
10
|
+
from sqliter import SqliterDB
|
|
11
|
+
from sqliter.exceptions import (
|
|
12
|
+
ForeignKeyConstraintError,
|
|
13
|
+
RecordInsertionError,
|
|
14
|
+
RecordNotFoundError,
|
|
15
|
+
SqliterError,
|
|
16
|
+
)
|
|
17
|
+
from sqliter.model import BaseDBModel
|
|
18
|
+
from sqliter.model.unique import unique
|
|
19
|
+
from sqliter.orm import BaseDBModel as ORMBaseDBModel
|
|
20
|
+
from sqliter.orm import ForeignKey
|
|
21
|
+
from sqliter.tui.demos.base import Demo, DemoCategory, extract_demo_code
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _run_record_not_found() -> str:
|
|
25
|
+
"""Handle attempts to access non-existent records.
|
|
26
|
+
|
|
27
|
+
RecordNotFoundError is raised when trying to get/update/delete
|
|
28
|
+
a record that doesn't exist.
|
|
29
|
+
"""
|
|
30
|
+
output = io.StringIO()
|
|
31
|
+
|
|
32
|
+
class User(BaseDBModel):
|
|
33
|
+
name: str
|
|
34
|
+
|
|
35
|
+
db = SqliterDB(memory=True)
|
|
36
|
+
db.create_table(User)
|
|
37
|
+
|
|
38
|
+
user = db.insert(User(name="Alice"))
|
|
39
|
+
output.write(f"Created user with pk={user.pk}\n")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# Try to delete non-existent record (raises RecordNotFoundError)
|
|
43
|
+
db.delete(User, 9999)
|
|
44
|
+
except RecordNotFoundError as e:
|
|
45
|
+
output.write(f"\nCaught error: {type(e).__name__}\n")
|
|
46
|
+
output.write(f"Message: {e}\n")
|
|
47
|
+
|
|
48
|
+
db.close()
|
|
49
|
+
return output.getvalue()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _run_unique_constraint() -> str:
|
|
53
|
+
"""Handle violations of unique field constraints.
|
|
54
|
+
|
|
55
|
+
RecordInsertionError is raised when inserting a duplicate value
|
|
56
|
+
into a field marked as unique().
|
|
57
|
+
"""
|
|
58
|
+
output = io.StringIO()
|
|
59
|
+
|
|
60
|
+
class User(BaseDBModel):
|
|
61
|
+
email: Annotated[str, unique()]
|
|
62
|
+
name: str
|
|
63
|
+
|
|
64
|
+
db = SqliterDB(memory=True)
|
|
65
|
+
db.create_table(User)
|
|
66
|
+
|
|
67
|
+
db.insert(User(email="alice@example.com", name="Alice"))
|
|
68
|
+
output.write("Created user with email alice@example.com\n")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# Try to insert duplicate email
|
|
72
|
+
db.insert(User(email="alice@example.com", name="Alice 2"))
|
|
73
|
+
except RecordInsertionError as e:
|
|
74
|
+
output.write(f"\nCaught error: {type(e).__name__}\n")
|
|
75
|
+
output.write(f"Message: {e}\n")
|
|
76
|
+
|
|
77
|
+
db.close()
|
|
78
|
+
return output.getvalue()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _run_validation_error() -> str:
|
|
82
|
+
"""Handle Pydantic validation errors.
|
|
83
|
+
|
|
84
|
+
ValidationError occurs when data doesn't match the field type
|
|
85
|
+
or constraints defined in the model.
|
|
86
|
+
"""
|
|
87
|
+
output = io.StringIO()
|
|
88
|
+
|
|
89
|
+
class Product(BaseDBModel):
|
|
90
|
+
name: str
|
|
91
|
+
price: float
|
|
92
|
+
quantity: int
|
|
93
|
+
|
|
94
|
+
db = SqliterDB(memory=True)
|
|
95
|
+
db.create_table(Product)
|
|
96
|
+
|
|
97
|
+
product = db.insert(Product(name="Widget", price=19.99, quantity=100))
|
|
98
|
+
output.write(f"Created product: {product.name}, price: ${product.price}\n")
|
|
99
|
+
|
|
100
|
+
# Try to create product with invalid data (wrong types)
|
|
101
|
+
output.write("\nAttempting to create product with invalid data...\n")
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# Wrong types: price should be float, quantity should be int
|
|
105
|
+
# ValidationError is raised by Pydantic during model instantiation
|
|
106
|
+
Product(name="Invalid Widget", price="free", quantity="lots")
|
|
107
|
+
except ValidationError as e:
|
|
108
|
+
output.write(f"\nCaught error: {type(e).__name__}\n")
|
|
109
|
+
output.write(f"Message: {e}\n")
|
|
110
|
+
|
|
111
|
+
db.close()
|
|
112
|
+
return output.getvalue()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _run_foreign_key_constraint() -> str:
|
|
116
|
+
"""Handle foreign key constraint violations.
|
|
117
|
+
|
|
118
|
+
ForeignKeyConstraintError occurs when referencing a non-existent
|
|
119
|
+
related record or violating referential integrity.
|
|
120
|
+
"""
|
|
121
|
+
output = io.StringIO()
|
|
122
|
+
|
|
123
|
+
class Author(ORMBaseDBModel):
|
|
124
|
+
name: str
|
|
125
|
+
|
|
126
|
+
class Book(ORMBaseDBModel):
|
|
127
|
+
title: str
|
|
128
|
+
author: ForeignKey[Author] = ForeignKey(Author, on_delete="RESTRICT")
|
|
129
|
+
|
|
130
|
+
db = SqliterDB(memory=True)
|
|
131
|
+
db.create_table(Author)
|
|
132
|
+
db.create_table(Book)
|
|
133
|
+
|
|
134
|
+
author = db.insert(Author(name="Jane"))
|
|
135
|
+
db.insert(Book(title="Book 1", author=author))
|
|
136
|
+
output.write("Created author and linked book\n")
|
|
137
|
+
|
|
138
|
+
# Attempt to insert book with non-existent author
|
|
139
|
+
output.write("\nAttempting to insert book with non-existent author...\n")
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
# Create book with invalid author_id (doesn't exist in database)
|
|
143
|
+
invalid_book = Book(title="Orphan Book", author_id=9999)
|
|
144
|
+
db.insert(invalid_book)
|
|
145
|
+
except ForeignKeyConstraintError as e:
|
|
146
|
+
output.write(f"\nCaught error: {type(e).__name__}\n")
|
|
147
|
+
output.write(f"Message: {e}\n")
|
|
148
|
+
|
|
149
|
+
db.close()
|
|
150
|
+
return output.getvalue()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _run_generic_error_handling() -> str:
|
|
154
|
+
"""Catch all SQLiter errors with the base SqliterError class.
|
|
155
|
+
|
|
156
|
+
Use SqliterError for generic error handling when you don't need
|
|
157
|
+
to distinguish between specific error types.
|
|
158
|
+
"""
|
|
159
|
+
output = io.StringIO()
|
|
160
|
+
|
|
161
|
+
class Task(BaseDBModel):
|
|
162
|
+
title: str
|
|
163
|
+
|
|
164
|
+
db = SqliterDB(memory=True)
|
|
165
|
+
db.create_table(Task)
|
|
166
|
+
|
|
167
|
+
task = db.insert(Task(title="My Task"))
|
|
168
|
+
output.write(f"Created task: {task.title}\n")
|
|
169
|
+
|
|
170
|
+
# Try to update a deleted record
|
|
171
|
+
try:
|
|
172
|
+
task.title = "Updated"
|
|
173
|
+
db.delete(Task, task.pk)
|
|
174
|
+
db.update(task) # This will fail
|
|
175
|
+
except SqliterError as e:
|
|
176
|
+
output.write(f"\nCaught SqliterError: {type(e).__name__}\n")
|
|
177
|
+
output.write(f"Message: {e}\n")
|
|
178
|
+
|
|
179
|
+
db.close()
|
|
180
|
+
return output.getvalue()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_category() -> DemoCategory:
|
|
184
|
+
"""Get the Error Handling demo category."""
|
|
185
|
+
return DemoCategory(
|
|
186
|
+
id="errors",
|
|
187
|
+
title="Error Handling",
|
|
188
|
+
icon="",
|
|
189
|
+
demos=[
|
|
190
|
+
Demo(
|
|
191
|
+
id="error_not_found",
|
|
192
|
+
title="Record Not Found",
|
|
193
|
+
description="Handle missing records gracefully",
|
|
194
|
+
category="errors",
|
|
195
|
+
code=extract_demo_code(_run_record_not_found),
|
|
196
|
+
execute=_run_record_not_found,
|
|
197
|
+
),
|
|
198
|
+
Demo(
|
|
199
|
+
id="error_unique",
|
|
200
|
+
title="Unique Constraint",
|
|
201
|
+
description="Handle duplicate value errors",
|
|
202
|
+
category="errors",
|
|
203
|
+
code=extract_demo_code(_run_unique_constraint),
|
|
204
|
+
execute=_run_unique_constraint,
|
|
205
|
+
),
|
|
206
|
+
Demo(
|
|
207
|
+
id="error_validation",
|
|
208
|
+
title="Validation Error",
|
|
209
|
+
description="Handle Pydantic validation errors",
|
|
210
|
+
category="errors",
|
|
211
|
+
code=extract_demo_code(_run_validation_error),
|
|
212
|
+
execute=_run_validation_error,
|
|
213
|
+
),
|
|
214
|
+
Demo(
|
|
215
|
+
id="error_fk",
|
|
216
|
+
title="Foreign Key Constraint",
|
|
217
|
+
description="Handle invalid foreign key references",
|
|
218
|
+
category="errors",
|
|
219
|
+
code=extract_demo_code(_run_foreign_key_constraint),
|
|
220
|
+
execute=_run_foreign_key_constraint,
|
|
221
|
+
),
|
|
222
|
+
Demo(
|
|
223
|
+
id="error_generic",
|
|
224
|
+
title="Generic Error Handling",
|
|
225
|
+
description="Catch-all for SQLiter errors",
|
|
226
|
+
category="errors",
|
|
227
|
+
code=extract_demo_code(_run_generic_error_handling),
|
|
228
|
+
execute=_run_generic_error_handling,
|
|
229
|
+
),
|
|
230
|
+
],
|
|
231
|
+
)
|