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.
Files changed (44) hide show
  1. sqliter/constants.py +4 -3
  2. sqliter/exceptions.py +43 -0
  3. sqliter/model/__init__.py +38 -3
  4. sqliter/model/foreign_key.py +153 -0
  5. sqliter/model/model.py +42 -3
  6. sqliter/model/unique.py +20 -11
  7. sqliter/orm/__init__.py +16 -0
  8. sqliter/orm/fields.py +412 -0
  9. sqliter/orm/foreign_key.py +8 -0
  10. sqliter/orm/model.py +243 -0
  11. sqliter/orm/query.py +221 -0
  12. sqliter/orm/registry.py +169 -0
  13. sqliter/query/query.py +720 -69
  14. sqliter/sqliter.py +533 -76
  15. sqliter/tui/__init__.py +62 -0
  16. sqliter/tui/__main__.py +6 -0
  17. sqliter/tui/app.py +179 -0
  18. sqliter/tui/demos/__init__.py +96 -0
  19. sqliter/tui/demos/base.py +114 -0
  20. sqliter/tui/demos/caching.py +283 -0
  21. sqliter/tui/demos/connection.py +150 -0
  22. sqliter/tui/demos/constraints.py +211 -0
  23. sqliter/tui/demos/crud.py +154 -0
  24. sqliter/tui/demos/errors.py +231 -0
  25. sqliter/tui/demos/field_selection.py +150 -0
  26. sqliter/tui/demos/filters.py +389 -0
  27. sqliter/tui/demos/models.py +248 -0
  28. sqliter/tui/demos/ordering.py +156 -0
  29. sqliter/tui/demos/orm.py +460 -0
  30. sqliter/tui/demos/results.py +241 -0
  31. sqliter/tui/demos/string_filters.py +210 -0
  32. sqliter/tui/demos/timestamps.py +126 -0
  33. sqliter/tui/demos/transactions.py +177 -0
  34. sqliter/tui/runner.py +116 -0
  35. sqliter/tui/styles/app.tcss +130 -0
  36. sqliter/tui/widgets/__init__.py +7 -0
  37. sqliter/tui/widgets/code_display.py +81 -0
  38. sqliter/tui/widgets/demo_list.py +65 -0
  39. sqliter/tui/widgets/output_display.py +92 -0
  40. {sqliter_py-0.9.0.dist-info → sqliter_py-0.16.0.dist-info}/METADATA +27 -11
  41. sqliter_py-0.16.0.dist-info/RECORD +47 -0
  42. {sqliter_py-0.9.0.dist-info → sqliter_py-0.16.0.dist-info}/WHEEL +2 -2
  43. sqliter_py-0.16.0.dist-info/entry_points.txt +3 -0
  44. sqliter_py-0.9.0.dist-info/RECORD +0 -14
@@ -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
+ )
@@ -0,0 +1,150 @@
1
+ """Field Selection 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_select_fields() -> str:
13
+ """Select specific fields from a query to reduce data transfer.
14
+
15
+ Use fields() to fetch only the columns you need, leaving unspecified
16
+ fields as None.
17
+ """
18
+ output = io.StringIO()
19
+
20
+ class User(BaseDBModel):
21
+ name: str
22
+ email: str
23
+ age: int
24
+ city: str
25
+
26
+ db = SqliterDB(memory=True)
27
+ db.create_table(User)
28
+
29
+ db.insert(User(name="Alice", email="alice@example.com", age=30, city="NYC"))
30
+ db.insert(User(name="Bob", email="bob@example.com", age=25, city="LA"))
31
+
32
+ # Select only name and email
33
+ users = db.select(User).fields(["name", "email"]).fetch_all()
34
+ output.write("Selected only name and email fields:\n")
35
+ for user in users:
36
+ output.write(f" - {user.name}, {user.email}\n")
37
+
38
+ # Note: age and city are None since they weren't selected
39
+ output.write("(age and city not selected, set to None)\n")
40
+
41
+ db.close()
42
+ return output.getvalue()
43
+
44
+
45
+ def _run_exclude_fields() -> str:
46
+ """Exclude specific fields from query results.
47
+
48
+ Use exclude() to fetch all fields except the ones you specify,
49
+ useful for hiding large or sensitive fields.
50
+ """
51
+ output = io.StringIO()
52
+
53
+ class Product(BaseDBModel):
54
+ name: str
55
+ price: float
56
+ description: str
57
+ stock: int
58
+
59
+ db = SqliterDB(memory=True)
60
+ db.create_table(Product)
61
+
62
+ db.insert(
63
+ Product(
64
+ name="Laptop",
65
+ price=999.99,
66
+ description="Fast laptop",
67
+ stock=10,
68
+ )
69
+ )
70
+
71
+ # Exclude description and stock
72
+ product = db.select(Product).exclude(["description", "stock"]).fetch_one()
73
+ if product is not None:
74
+ output.write(f"Product: {product.name}\n")
75
+ output.write(f"Price: ${product.price}\n")
76
+ output.write("(description and stock excluded)\n")
77
+
78
+ db.close()
79
+ return output.getvalue()
80
+
81
+
82
+ def _run_only_field() -> str:
83
+ """Select a single field from query results.
84
+
85
+ Use only() when you only need one specific field from your query,
86
+ useful for getting IDs or names.
87
+ """
88
+ output = io.StringIO()
89
+
90
+ class Task(BaseDBModel):
91
+ title: str
92
+ status: str
93
+ priority: int
94
+ assigned_to: str
95
+
96
+ db = SqliterDB(memory=True)
97
+ db.create_table(Task)
98
+
99
+ db.insert(
100
+ Task(title="Fix bug", status="todo", priority=1, assigned_to="Alice")
101
+ )
102
+ db.insert(
103
+ Task(title="Add feature", status="done", priority=2, assigned_to="Bob")
104
+ )
105
+
106
+ # Select only the title field
107
+ tasks = db.select(Task).only("title").fetch_all()
108
+ output.write("Selected only title field:\n")
109
+ for task in tasks:
110
+ output.write(f" - {task.title}\n")
111
+
112
+ output.write("(status, priority, assigned_to not selected)\n")
113
+
114
+ db.close()
115
+ return output.getvalue()
116
+
117
+
118
+ def get_category() -> DemoCategory:
119
+ """Get the Field Selection demo category."""
120
+ return DemoCategory(
121
+ id="field_selection",
122
+ title="Field Selection",
123
+ icon="",
124
+ demos=[
125
+ Demo(
126
+ id="field_select",
127
+ title="Select Fields",
128
+ description="Choose specific fields to fetch",
129
+ category="field_selection",
130
+ code=extract_demo_code(_run_select_fields),
131
+ execute=_run_select_fields,
132
+ ),
133
+ Demo(
134
+ id="field_exclude",
135
+ title="Exclude Fields",
136
+ description="Exclude specific fields from results",
137
+ category="field_selection",
138
+ code=extract_demo_code(_run_exclude_fields),
139
+ execute=_run_exclude_fields,
140
+ ),
141
+ Demo(
142
+ id="field_only",
143
+ title="Select Single Field",
144
+ description="Fetch only one specific field",
145
+ category="field_selection",
146
+ code=extract_demo_code(_run_only_field),
147
+ execute=_run_only_field,
148
+ ),
149
+ ],
150
+ )