sqlite-database 0.7.2__tar.gz → 0.7.4__tar.gz
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.
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/workflows/python-publish.yml +1 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.gitignore +4 -1
- {sqlite_database-0.7.2/sqlite_database.egg-info → sqlite_database-0.7.4}/PKG-INFO +1 -1
- sqlite_database-0.7.4/docs/ModelAPI.md +261 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/SimpleGuide.md +6 -4
- sqlite_database-0.7.4/docs/_.md +251 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/index.rst +1 -2
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/pyproject.toml +1 -1
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/__init__.py +3 -3
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/database.py +34 -3
- {sqlite_database-0.7.2/sqlite_database/model → sqlite_database-0.7.4/sqlite_database/models}/__init__.py +27 -4
- {sqlite_database-0.7.2/sqlite_database/model → sqlite_database-0.7.4/sqlite_database/models}/errors.py +12 -9
- {sqlite_database-0.7.2/sqlite_database/model → sqlite_database-0.7.4/sqlite_database/models}/helpers.py +3 -3
- {sqlite_database-0.7.2/sqlite_database/model → sqlite_database-0.7.4/sqlite_database/models}/query_builder.py +18 -5
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/table.py +10 -1
- {sqlite_database-0.7.2 → sqlite_database-0.7.4/sqlite_database.egg-info}/PKG-INFO +1 -1
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database.egg-info/SOURCES.txt +21 -8
- sqlite_database-0.7.4/tests/database/__init__.py +0 -0
- sqlite_database-0.7.4/tests/database/model_api/__init__.py +0 -0
- sqlite_database-0.7.4/tests/database/model_api/test_model_api.py +197 -0
- sqlite_database-0.7.4/tests/database/setup.py +159 -0
- sqlite_database-0.7.4/tests/database/table_api/__init__.py +0 -0
- sqlite_database-0.7.4/tests/database/table_api/test_csv.py +28 -0
- sqlite_database-0.7.4/tests/database/table_api/test_delete.py +43 -0
- sqlite_database-0.7.4/tests/database/table_api/test_insert.py +38 -0
- sqlite_database-0.7.4/tests/database/table_api/test_others.py +65 -0
- sqlite_database-0.7.4/tests/database/table_api/test_select.py +102 -0
- sqlite_database-0.7.4/tests/database/table_api/test_update.py +58 -0
- sqlite_database-0.7.4/tests/database/test_custom.py +33 -0
- sqlite_database-0.7.4/transient/README.md +1 -0
- sqlite_database-0.7.2/docs/usage.md +0 -315
- sqlite_database-0.7.2/tests/test_database.py +0 -639
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.editorconfig +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/dependabot.yml +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/workflows/pylint.yml +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.github/workflows/pytest.yml +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.readthedocs.yaml +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/.vscode/settings.json +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/Features.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/History.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/LICENSE +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/README.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/SimpleGuide.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/TODO.md +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/activate +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/check.bat +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/check.sh +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/include/utility.bash +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/install.bash +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/need-installed/activate +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/need-installed/pre-commit +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/bin/summarize-pylint.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/dev-config/black.toml +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/dev-config/pylint.toml +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/dev-config/pytest.ini +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/dev-requirements.txt +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/Makefile +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/api_reference.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/conf.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/make.bat +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/modules.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.column.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.config.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.csv.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.database.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.errors.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.functions.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.locals.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.model.errors.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.model.helpers.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.model.query_builder.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.model.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.operators.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.query_builder.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.signature.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.subexp.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.table.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.typings.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs/sqlite_database.utils.rst +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/docs-requirements.txt +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/project-init.bash +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/setup.cfg +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/setup.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/_debug.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/_utils.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/column.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/csv.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/errors.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/functions.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/locals.py +0 -0
- {sqlite_database-0.7.2/sqlite_database/model → sqlite_database-0.7.4/sqlite_database/models}/mixin.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/operators.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/query_builder.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/signature.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/subquery.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/typings.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database/utils.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database.egg-info/dependency_links.txt +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database.egg-info/requires.txt +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database.egg-info/top_level.txt +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/sqlite_database.egg-info/zip-safe +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/tests/__init__.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/tests/manual_test_performances.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/tests/test_internals.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/tests/user_benchmark.py +0 -0
- {sqlite_database-0.7.2 → sqlite_database-0.7.4}/tests/user_helpers.py +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# 📘 ModelAPI – A Friendly Guide to a Lightweight SQLite ORM
|
|
2
|
+
|
|
3
|
+
> **Note**: ModelAPI uses a different structure than TableAPI! So if you're coming from that, treat this as a fresh start.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📚 Table of Contents
|
|
8
|
+
|
|
9
|
+
- [📘 ModelAPI – A Friendly Guide to a Lightweight SQLite ORM](#-modelapi--a-friendly-guide-to-a-lightweight-sqlite-orm)
|
|
10
|
+
- [📚 Table of Contents](#-table-of-contents)
|
|
11
|
+
- [🧠 Introduction](#-introduction)
|
|
12
|
+
- [🚀 Getting Started](#-getting-started)
|
|
13
|
+
- [1. Bootstrapping the Database](#1-bootstrapping-the-database)
|
|
14
|
+
- [2. Creating a Model](#2-creating-a-model)
|
|
15
|
+
- [💡 How it works](#-how-it-works)
|
|
16
|
+
- [3. Running CRUD Operations](#3-running-crud-operations)
|
|
17
|
+
- [CREATE](#create)
|
|
18
|
+
- [READ](#read)
|
|
19
|
+
- [Advanced `.where()` filtering](#advanced-where-filtering)
|
|
20
|
+
- [UPDATE](#update)
|
|
21
|
+
- [DELETE](#delete)
|
|
22
|
+
- [🎮 Example App – CRUD in Action](#-example-app--crud-in-action)
|
|
23
|
+
- [🙋 FAQ](#-faq)
|
|
24
|
+
- [💡 Tips \& Notes](#-tips--notes)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🧠 Introduction
|
|
29
|
+
|
|
30
|
+
**ModelAPI** gives you a Laravel-style, class-based ORM interface for SQLite in Python. It's minimal, fast, and easy to use.
|
|
31
|
+
|
|
32
|
+
Think of it like this:
|
|
33
|
+
|
|
34
|
+
- You define your data with Python classes.
|
|
35
|
+
- The ORM handles schema definition, inserts, reads, updates, and deletes.
|
|
36
|
+
- It's flexible enough for small scripts and powerful enough for CLI tools.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 Getting Started
|
|
41
|
+
|
|
42
|
+
Let’s walk through setting things up step-by-step!
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### 1. Bootstrapping the Database
|
|
47
|
+
|
|
48
|
+
Create a file like `db.py` to initialize your SQLite database.
|
|
49
|
+
|
|
50
|
+
```py
|
|
51
|
+
# db.py
|
|
52
|
+
from sqlite_database import Database
|
|
53
|
+
|
|
54
|
+
db = Database(":memory:") # or use "your_file.db" to persist data
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This creates a simple in-memory SQLite database. For persistent storage, pass a filename instead of `":memory:"`.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 2. Creating a Model
|
|
62
|
+
|
|
63
|
+
Each model is a Python class with typed fields and a schema definition.
|
|
64
|
+
|
|
65
|
+
```py
|
|
66
|
+
# model/notes.py
|
|
67
|
+
from uuid import uuid4
|
|
68
|
+
from sqlite_database import model, BaseModel, Primary
|
|
69
|
+
from ..db import db
|
|
70
|
+
|
|
71
|
+
@model(db)
|
|
72
|
+
class Notes(BaseModel):
|
|
73
|
+
__schema__ = (Primary('id'),)
|
|
74
|
+
__auto_id__ = lambda: str(uuid4())
|
|
75
|
+
|
|
76
|
+
id: str
|
|
77
|
+
title: str
|
|
78
|
+
content: str
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### 💡 How it works
|
|
82
|
+
|
|
83
|
+
- `@model(db)` binds the class to the database.
|
|
84
|
+
- `__schema__` defines your table schema. You can use `Primary()`, `Unique()`, `Foreign()`, etc.
|
|
85
|
+
- `__auto_id__` is optional. It helps generate IDs automatically (e.g. UUIDs).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### 3. Running CRUD Operations
|
|
90
|
+
|
|
91
|
+
#### CREATE
|
|
92
|
+
|
|
93
|
+
```py
|
|
94
|
+
Notes.create(title="Hello", content="World!")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
You can gather user input too:
|
|
98
|
+
|
|
99
|
+
```py
|
|
100
|
+
title = input('Title: ')
|
|
101
|
+
content = input('Content: ')
|
|
102
|
+
Notes.create(title=title, content=content)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
#### READ
|
|
108
|
+
|
|
109
|
+
You can fetch all rows, one row, or use filters:
|
|
110
|
+
|
|
111
|
+
```py
|
|
112
|
+
Notes.all() # Returns a list of all notes
|
|
113
|
+
Notes.first(id="123") # Returns the first match or None
|
|
114
|
+
Notes.one(id="123") # Returns exactly one result, else throws error
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
##### Advanced `.where()` filtering
|
|
118
|
+
|
|
119
|
+
```py
|
|
120
|
+
# Find a note by title
|
|
121
|
+
note = Notes.where(title="Shopping List").fetch_one()
|
|
122
|
+
|
|
123
|
+
# Limit or offset
|
|
124
|
+
Notes.where().limit(5).fetch() # Get top 5 notes
|
|
125
|
+
Notes.where().offset(5).fetch() # Skip 5 and get the rest
|
|
126
|
+
|
|
127
|
+
# Count matching rows
|
|
128
|
+
count = Notes.where().count()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
#### UPDATE
|
|
134
|
+
|
|
135
|
+
Updating a row is super simple. Fetch it first, then call `.update()`:
|
|
136
|
+
|
|
137
|
+
```py
|
|
138
|
+
note = Notes.first(id="some-id")
|
|
139
|
+
note.update(title="Updated", content="Updated content here.")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
#### DELETE
|
|
145
|
+
|
|
146
|
+
Delete just like you'd update:
|
|
147
|
+
|
|
148
|
+
```py
|
|
149
|
+
note = Notes.first(id="some-id")
|
|
150
|
+
note.delete()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 🎮 Example App – CRUD in Action
|
|
156
|
+
|
|
157
|
+
Here’s a small CLI app to tie it all together:
|
|
158
|
+
|
|
159
|
+
```py
|
|
160
|
+
from enum import IntEnum
|
|
161
|
+
from uuid import uuid4
|
|
162
|
+
from sqlite_database import Database, model, BaseModel, Primary
|
|
163
|
+
|
|
164
|
+
db = Database(":memory:")
|
|
165
|
+
|
|
166
|
+
@model(db)
|
|
167
|
+
class Notes(BaseModel):
|
|
168
|
+
__schema__ = (Primary('id'),)
|
|
169
|
+
__auto_id__ = lambda: str(uuid4())
|
|
170
|
+
|
|
171
|
+
id: str
|
|
172
|
+
title: str
|
|
173
|
+
content: str
|
|
174
|
+
|
|
175
|
+
def display():
|
|
176
|
+
print('-'*3)
|
|
177
|
+
for note in Notes.all():
|
|
178
|
+
print(f"ID : {note.id}")
|
|
179
|
+
print(f"Title : {note.title}")
|
|
180
|
+
print(f"Content : {note.content}")
|
|
181
|
+
print("-"*3)
|
|
182
|
+
|
|
183
|
+
def create():
|
|
184
|
+
title = input("Title: ")
|
|
185
|
+
content = input("Content: ")
|
|
186
|
+
Notes.create(title=title, content=content)
|
|
187
|
+
|
|
188
|
+
def update():
|
|
189
|
+
note_id = input('ID: ')
|
|
190
|
+
note = Notes.first(id=note_id)
|
|
191
|
+
if note:
|
|
192
|
+
title = input("New title: ")
|
|
193
|
+
content = input("New content: ")
|
|
194
|
+
note.update(title=title, content=content)
|
|
195
|
+
else:
|
|
196
|
+
print("Note not found.")
|
|
197
|
+
|
|
198
|
+
def delete():
|
|
199
|
+
note_id = input('ID: ')
|
|
200
|
+
note = Notes.first(id=note_id)
|
|
201
|
+
if note:
|
|
202
|
+
note.delete()
|
|
203
|
+
else:
|
|
204
|
+
print("Note not found.")
|
|
205
|
+
|
|
206
|
+
class CMD(IntEnum):
|
|
207
|
+
DISPLAY = 1
|
|
208
|
+
CREATE = 2
|
|
209
|
+
UPDATE = 3
|
|
210
|
+
DELETE = 4
|
|
211
|
+
EXIT = 5
|
|
212
|
+
|
|
213
|
+
def main():
|
|
214
|
+
while True:
|
|
215
|
+
print('-'*8)
|
|
216
|
+
print('1. Display all notes')
|
|
217
|
+
print('2. Create a note')
|
|
218
|
+
print('3. Update a note')
|
|
219
|
+
print('4. Delete a note')
|
|
220
|
+
print('5. Exit')
|
|
221
|
+
try:
|
|
222
|
+
cmd = int(input("Command: "))
|
|
223
|
+
if cmd == CMD.DISPLAY:
|
|
224
|
+
display()
|
|
225
|
+
elif cmd == CMD.CREATE:
|
|
226
|
+
create()
|
|
227
|
+
elif cmd == CMD.UPDATE:
|
|
228
|
+
update()
|
|
229
|
+
elif cmd == CMD.DELETE:
|
|
230
|
+
delete()
|
|
231
|
+
elif cmd == CMD.EXIT:
|
|
232
|
+
break
|
|
233
|
+
except KeyboardInterrupt:
|
|
234
|
+
break
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
print(f"{type(exc).__name__}: {exc}")
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
main()
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 🙋 FAQ
|
|
245
|
+
|
|
246
|
+
**Q: Can I define relationships between models?**
|
|
247
|
+
A: Yes, using `Foreign()` in `__schema__`. This doc will be updated with examples soon.
|
|
248
|
+
|
|
249
|
+
**Q: Does it support migrations?**
|
|
250
|
+
A: Not directly. The schema is defined per-model, so migrations require manual changes.
|
|
251
|
+
|
|
252
|
+
**Q: How are models stored?**
|
|
253
|
+
A: The models reflect SQLite tables. Everything is automatically synced on class definition.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 💡 Tips & Notes
|
|
258
|
+
|
|
259
|
+
- You can use any primitive Python types (e.g. `int`, `str`, `float`) in your model class.
|
|
260
|
+
- Keep `__auto_id__` short and efficient — UUIDs are recommended.
|
|
261
|
+
- Don’t forget to call `Database()` only once and reuse the instance.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# SQLite Database (Beginner-Friendly Guide)
|
|
1
|
+
# SQLite Database (Beginner-Friendly Guide to Table API)
|
|
2
2
|
|
|
3
3
|
This library is a simple wrapper for Python's built-in SQLite package. It simplifies database operations, making it easier to interact with SQLite without writing complex SQL queries.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
|
-
- [SQLite Database (Beginner-Friendly Guide)](#sqlite-database-beginner-friendly-guide)
|
|
7
|
+
- [SQLite Database (Beginner-Friendly Guide to Table API)](#sqlite-database-beginner-friendly-guide-to-table-api)
|
|
8
8
|
- [Table of Contents](#table-of-contents)
|
|
9
9
|
- [Introduction](#introduction)
|
|
10
10
|
- [Getting Started](#getting-started)
|
|
@@ -39,6 +39,8 @@ SQLite is a small and fast database engine that stores all data in a single file
|
|
|
39
39
|
|
|
40
40
|
This library itself is intended to help around your life from having to write SQL statements unless in specific cases. For now, implemented API is Table API which is what is this documentation will show to you.
|
|
41
41
|
|
|
42
|
+
There's 2 taste what we've developed, both are named as "TableAPI" and "ModelAPI". Refer to [this for ModelAPI](./ModelAPI.md)
|
|
43
|
+
|
|
42
44
|
### Installation
|
|
43
45
|
|
|
44
46
|
If the library is available on PyPI, install it using:
|
|
@@ -107,8 +109,8 @@ print(all_users) # Output: [Row(id=1, name='Alice'), Row(id=2, name='Bob'), Row
|
|
|
107
109
|
To fetch only names:
|
|
108
110
|
|
|
109
111
|
```python
|
|
110
|
-
user_names = users.select(
|
|
111
|
-
print(user_names) # Output: [
|
|
112
|
+
user_names = users.select(what=("name",))
|
|
113
|
+
print(user_names) # Output: ['Alice', 'Bob', 'Charlie']
|
|
112
114
|
```
|
|
113
115
|
|
|
114
116
|
### Updating Data
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# 🍃 ModelAPI: Lightweight SQLite ORM
|
|
2
|
+
|
|
3
|
+
A simple, model-centric ORM for SQLite that feels familiar if you've used Laravel's Eloquent. Designed for fast prototyping and projects that value readability and minimalism.
|
|
4
|
+
|
|
5
|
+
> **Heads up:** `ModelAPI` is **not** the same as `TableAPI`. While both serve as ORMs, `ModelAPI` focuses on declarative, class-based models — ideal for those who prefer OOP-style code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📘 Table of Contents
|
|
10
|
+
|
|
11
|
+
- [🍃 ModelAPI: Lightweight SQLite ORM](#-modelapi-lightweight-sqlite-orm)
|
|
12
|
+
- [📘 Table of Contents](#-table-of-contents)
|
|
13
|
+
- [🚀 Getting Started](#-getting-started)
|
|
14
|
+
- [🧩 Installation](#-installation)
|
|
15
|
+
- [🛠 Database Setup](#-database-setup)
|
|
16
|
+
- [🧱 Defining Models](#-defining-models)
|
|
17
|
+
- [🏷 `__schema__` and Field Declarations](#-__schema__-and-field-declarations)
|
|
18
|
+
- [🔁 Auto-Generating IDs](#-auto-generating-ids)
|
|
19
|
+
- [🛠 CRUD Operations](#-crud-operations)
|
|
20
|
+
- [✅ Create](#-create)
|
|
21
|
+
- [🔍 Read](#-read)
|
|
22
|
+
- [`all()`, `first()`, and `one()`](#all-first-and-one)
|
|
23
|
+
- [Flexible Queries with `where()`](#flexible-queries-with-where)
|
|
24
|
+
- [📝 Update](#-update)
|
|
25
|
+
- [🗑 Delete](#-delete)
|
|
26
|
+
- [💻 CLI Example](#-cli-example)
|
|
27
|
+
- [🧠 Best Practices](#-best-practices)
|
|
28
|
+
- [⚠️ Common Pitfalls](#️-common-pitfalls)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🚀 Getting Started
|
|
33
|
+
|
|
34
|
+
### 🧩 Installation
|
|
35
|
+
|
|
36
|
+
You’ll need the core dependency, `sqlite-database`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install sqlite-database
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If you're developing locally and already have it, just ensure it's accessible in your Python path.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### 🛠 Database Setup
|
|
47
|
+
|
|
48
|
+
Start by initializing your database:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# db.py
|
|
52
|
+
from sqlite_database import Database
|
|
53
|
+
|
|
54
|
+
db = Database("notes.db") # Or use ":memory:" for an in-memory DB
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You’ll reuse `db` across all your models.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🧱 Defining Models
|
|
62
|
+
|
|
63
|
+
Models define how your data is structured. Think of each model as a table blueprint.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# model/notes.py
|
|
67
|
+
from sqlite_database import model, BaseModel, Primary
|
|
68
|
+
from db import db
|
|
69
|
+
|
|
70
|
+
@model(db)
|
|
71
|
+
class Notes(BaseModel):
|
|
72
|
+
__schema__ = (Primary('id'),)
|
|
73
|
+
|
|
74
|
+
id: str
|
|
75
|
+
title: str
|
|
76
|
+
content: str
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 🏷 `__schema__` and Field Declarations
|
|
80
|
+
|
|
81
|
+
Define your fields using schema helpers like:
|
|
82
|
+
|
|
83
|
+
- `Primary(field_name)`
|
|
84
|
+
- `Unique(field_name)`
|
|
85
|
+
- `Foreign(field_name, f"{ref_table}/{ref_column}")`
|
|
86
|
+
|
|
87
|
+
These help ensure integrity and enforce constraints under the hood.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### 🔁 Auto-Generating IDs
|
|
92
|
+
|
|
93
|
+
If your primary key is a UUID or something dynamic, define `__auto_id__`:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from uuid import uuid4
|
|
97
|
+
|
|
98
|
+
@model(db)
|
|
99
|
+
class Notes(BaseModel):
|
|
100
|
+
__schema__ = (Primary("id"),)
|
|
101
|
+
__auto_id__ = lambda: str(uuid4())
|
|
102
|
+
|
|
103
|
+
id: str
|
|
104
|
+
title: str
|
|
105
|
+
content: str
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Whenever you call `.create()` without an `id`, this auto-generator kicks in.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 🛠 CRUD Operations
|
|
113
|
+
|
|
114
|
+
### ✅ Create
|
|
115
|
+
|
|
116
|
+
Create a new record like this:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
Notes.create(title="Meeting", content="Discuss roadmap")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Interactive input? No problem:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
title = input("Title: ")
|
|
126
|
+
content = input("Content: ")
|
|
127
|
+
Notes.create(title=title, content=content)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### 🔍 Read
|
|
133
|
+
|
|
134
|
+
#### `all()`, `first()`, and `one()`
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
Notes.all() # Returns a list of all notes
|
|
138
|
+
Notes.first(id="abc") # First match or None
|
|
139
|
+
Notes.one(id="abc") # Exactly one match; raises error if 0 or >1
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Flexible Queries with `where()`
|
|
143
|
+
|
|
144
|
+
Chainable query builder:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
Notes.where(title="Roadmap").fetch_one()
|
|
148
|
+
Notes.where().limit(5).fetch()
|
|
149
|
+
Notes.where().offset(5).fetch()
|
|
150
|
+
Notes.where().count()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### 📝 Update
|
|
156
|
+
|
|
157
|
+
First, fetch a record — then update it:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
note = Notes.first(id="abc")
|
|
161
|
+
if note:
|
|
162
|
+
note.update(title="Updated title", content="Updated body")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### 🗑 Delete
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
note = Notes.first(id="abc")
|
|
171
|
+
if note:
|
|
172
|
+
note.delete()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This permanently removes the record from the database.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 💻 CLI Example
|
|
180
|
+
|
|
181
|
+
Here’s a complete interactive CLI app:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# cli.py
|
|
185
|
+
from model.notes import Notes
|
|
186
|
+
from enum import IntEnum
|
|
187
|
+
|
|
188
|
+
class CMD(IntEnum):
|
|
189
|
+
DISPLAY = 1
|
|
190
|
+
CREATE = 2
|
|
191
|
+
UPDATE = 3
|
|
192
|
+
DELETE = 4
|
|
193
|
+
EXIT = 5
|
|
194
|
+
|
|
195
|
+
def display():
|
|
196
|
+
print("---")
|
|
197
|
+
for note in Notes.all():
|
|
198
|
+
print(f"ID: {note.id}\nTitle: {note.title}\nContent: {note.content}\n---")
|
|
199
|
+
|
|
200
|
+
def create():
|
|
201
|
+
Notes.create(title=input("Title: "), content=input("Content: "))
|
|
202
|
+
|
|
203
|
+
def update():
|
|
204
|
+
note = Notes.first(id=input("ID: "))
|
|
205
|
+
if note:
|
|
206
|
+
note.update(title=input("Title: "), content=input("Content: "))
|
|
207
|
+
else:
|
|
208
|
+
print("Note not found.")
|
|
209
|
+
|
|
210
|
+
def delete():
|
|
211
|
+
note = Notes.first(id=input("ID: "))
|
|
212
|
+
if note:
|
|
213
|
+
note.delete()
|
|
214
|
+
else:
|
|
215
|
+
print("Note not found.")
|
|
216
|
+
|
|
217
|
+
def main():
|
|
218
|
+
while True:
|
|
219
|
+
print("1. Display\n2. Create\n3. Update\n4. Delete\n5. Exit")
|
|
220
|
+
try:
|
|
221
|
+
cmd = int(input("Select: "))
|
|
222
|
+
if cmd == CMD.DISPLAY: display()
|
|
223
|
+
elif cmd == CMD.CREATE: create()
|
|
224
|
+
elif cmd == CMD.UPDATE: update()
|
|
225
|
+
elif cmd == CMD.DELETE: delete()
|
|
226
|
+
elif cmd == CMD.EXIT: break
|
|
227
|
+
except Exception as e:
|
|
228
|
+
print(f"{type(e).__name__}: {e}")
|
|
229
|
+
|
|
230
|
+
if __name__ == '__main__':
|
|
231
|
+
main()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 🧠 Best Practices
|
|
237
|
+
|
|
238
|
+
✅ **Define all constraints in `__schema__`** — Primary, Foreign, and Unique.
|
|
239
|
+
✅ **Use `__auto_id__`** for consistent ID generation (especially UUIDs).
|
|
240
|
+
✅ **Keep models minimal** — push business logic elsewhere (CLI, service layer, etc).
|
|
241
|
+
✅ Use `.where().count()` instead of loading all records just to count them.
|
|
242
|
+
✅ Only use `.one()` when you’re 100% sure the result is unique.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## ⚠️ Common Pitfalls
|
|
247
|
+
|
|
248
|
+
❌ Missing `@model(db)` — your model won’t be registered.
|
|
249
|
+
❌ Using `.one()` on multi-match queries — it will throw an exception.
|
|
250
|
+
❌ Forgetting `.fetch()` or `.fetch_one()` after `.where()` — it won’t run.
|
|
251
|
+
❌ Assuming `.create()` returns an object — it returns `None`.
|
|
@@ -25,7 +25,7 @@ Repository = "https://github.com/RimuEirnarn/sqlite_database.git"
|
|
|
25
25
|
|
|
26
26
|
[tool.setuptools]
|
|
27
27
|
zip-safe = true
|
|
28
|
-
packages = ["sqlite_database", "sqlite_database.
|
|
28
|
+
packages = ["sqlite_database", "sqlite_database.models"]
|
|
29
29
|
|
|
30
30
|
[project.optional-dependencies]
|
|
31
31
|
dev = ["pytest", "pylint", "black"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Database"""
|
|
2
2
|
|
|
3
|
-
from .
|
|
3
|
+
from .models import BaseModel, model, Foreign, Primary, Unique
|
|
4
4
|
from .database import Database
|
|
5
5
|
from ._utils import null, Row, Null
|
|
6
6
|
from .column import Column, text, integer, blob, real
|
|
@@ -14,7 +14,7 @@ def test_installed():
|
|
|
14
14
|
return True
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
__version__ = "0.7.
|
|
17
|
+
__version__ = "0.7.4"
|
|
18
18
|
__all__ = [
|
|
19
19
|
"Database",
|
|
20
20
|
"Table",
|
|
@@ -29,7 +29,7 @@ __all__ = [
|
|
|
29
29
|
"real",
|
|
30
30
|
"blob",
|
|
31
31
|
"BaseModel",
|
|
32
|
-
"
|
|
32
|
+
"models",
|
|
33
33
|
"Foreign",
|
|
34
34
|
"Primary",
|
|
35
35
|
"Unique"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""SQLite Database"""
|
|
2
2
|
|
|
3
3
|
from atexit import register as finalize
|
|
4
|
-
from sqlite3 import OperationalError, connect
|
|
4
|
+
from sqlite3 import OperationalError, connect, Connection
|
|
5
|
+
from threading import local
|
|
5
6
|
from typing import Iterable, Literal, Optional
|
|
6
7
|
|
|
7
8
|
from sqlite_database._debug import if_debug_print
|
|
@@ -43,14 +44,17 @@ class Database: # pylint: disable=too-many-instance-attributes
|
|
|
43
44
|
del kwargs['forgive']
|
|
44
45
|
if 'strict' in kwargs:
|
|
45
46
|
del kwargs['strict']
|
|
46
|
-
self._database = connect(path, **kwargs)
|
|
47
|
-
self._database.row_factory = dict_factory
|
|
48
47
|
self._config = None
|
|
49
48
|
self._closed = False
|
|
50
49
|
if not self._closed or self.__dict__.get("_initiated", False) is False:
|
|
51
50
|
finalize(self._finalizer)
|
|
52
51
|
self._initiated = True
|
|
53
52
|
self._kwargs = kwargs
|
|
53
|
+
self._create_connection()
|
|
54
|
+
|
|
55
|
+
def _create_connection(self):
|
|
56
|
+
self._database = connect(self._path, **self._kwargs)
|
|
57
|
+
self._database.row_factory = dict_factory
|
|
54
58
|
|
|
55
59
|
def _finalizer(self):
|
|
56
60
|
self.close()
|
|
@@ -224,3 +228,30 @@ class Database: # pylint: disable=too-many-instance-attributes
|
|
|
224
228
|
def sql(self):
|
|
225
229
|
"""SQL Connection"""
|
|
226
230
|
return self._database
|
|
231
|
+
|
|
232
|
+
class AsyncDatabase(Database):
|
|
233
|
+
"""Async (threads, subprocess) ready"""
|
|
234
|
+
|
|
235
|
+
def __init__(self, path: str, **kwargs) -> None:
|
|
236
|
+
super().__init__(path, **kwargs)
|
|
237
|
+
self._local = local()
|
|
238
|
+
|
|
239
|
+
def _create_connection(self):
|
|
240
|
+
conn = getattr(self._local, "conn", None)
|
|
241
|
+
if conn is None:
|
|
242
|
+
timeout = self._kwargs.pop('timeout', 30)
|
|
243
|
+
conn = connect(
|
|
244
|
+
self._path,
|
|
245
|
+
timeout=timeout,
|
|
246
|
+
isolation_level=self._kwargs.pop("isolation_level", None),
|
|
247
|
+
check_same_thread=self._kwargs.pop("check_same_thread", False)
|
|
248
|
+
)
|
|
249
|
+
conn.row_factory = dict_factory
|
|
250
|
+
conn.execute("PRAGMA journal_mode=WAL;")
|
|
251
|
+
if isinstance(timeout, int):
|
|
252
|
+
conn.execute(f'PRAGMA busy_timeout={timeout * 1000};')
|
|
253
|
+
self._local.conn = conn
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def _database(self) -> Connection:
|
|
257
|
+
return self._local.conn
|