sqlengine-lite 2.1.1__tar.gz → 2.2.1__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.
Files changed (27) hide show
  1. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/PKG-INFO +94 -20
  2. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/README.md +93 -19
  3. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/pyproject.toml +8 -2
  4. sqlengine_lite-2.2.1/src/sqlengine/__init__.py +11 -0
  5. sqlengine_lite-2.2.1/src/sqlengine/_internal/__init__.py +4 -0
  6. sqlengine_lite-2.2.1/src/sqlengine/_internal/connection_manager.py +266 -0
  7. {sqlengine_lite-2.1.1/src/sqlengine/utils → sqlengine_lite-2.2.1/src/sqlengine/_internal}/repr.py +1 -30
  8. {sqlengine_lite-2.1.1/src/sqlengine/utils → sqlengine_lite-2.2.1/src/sqlengine/_internal}/sqlgen.py +2 -4
  9. {sqlengine_lite-2.1.1/src/sqlengine/utils → sqlengine_lite-2.2.1/src/sqlengine/_internal}/statements.py +71 -49
  10. sqlengine_lite-2.2.1/src/sqlengine/_internal/types.py +105 -0
  11. sqlengine_lite-2.2.1/src/sqlengine/exceptions.py +20 -0
  12. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/src/sqlengine/schema.py +1 -1
  13. sqlengine_lite-2.2.1/src/sqlengine/sqltable.py +511 -0
  14. sqlengine_lite-2.2.1/src/sqlengine/utils/__init__.py +4 -0
  15. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/src/sqlengine/utils/connection.py +29 -20
  16. sqlengine_lite-2.2.1/src/sqlengine/utils/convert.py +129 -0
  17. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/src/sqlengine_lite.egg-info/PKG-INFO +94 -20
  18. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/src/sqlengine_lite.egg-info/SOURCES.txt +8 -4
  19. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/tests/test.py +281 -45
  20. sqlengine_lite-2.1.1/src/sqlengine/__init__.py +0 -7
  21. sqlengine_lite-2.1.1/src/sqlengine/sqltable.py +0 -593
  22. sqlengine_lite-2.1.1/src/sqlengine/utils/__init__.py +0 -5
  23. sqlengine_lite-2.1.1/src/sqlengine/utils/types.py +0 -65
  24. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/LICENSE +0 -0
  25. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/setup.cfg +0 -0
  26. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/src/sqlengine_lite.egg-info/dependency_links.txt +0 -0
  27. {sqlengine_lite-2.1.1 → sqlengine_lite-2.2.1}/src/sqlengine_lite.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlengine-lite
3
- Version: 2.1.1
3
+ Version: 2.2.1
4
4
  Summary: Cute sqlite3 wrapper for sql tables
5
5
  Project-URL: Homepage, https://github.com/suffermuffin/SQL-Engine
6
6
  Project-URL: Repository, https://github.com/suffermuffin/SQL-Engine.git
@@ -25,7 +25,9 @@ Dynamic: license-file
25
25
  - [Delete Query](#delete-query)
26
26
  - [Transaction](#transaction)
27
27
  - [Get Item](#get-item)
28
+ - [Custom Types](#custom-types)
28
29
  - [Csv Converter](#csv-converter)
30
+ - [Pandas-like Converter](#pandas-like-converter)
29
31
  - [Full Documentation](#full-documentation)
30
32
 
31
33
 
@@ -44,17 +46,21 @@ Sql-Engine implements Jupyter integration and dynamic schema building. You can e
44
46
  ```py
45
47
  from sqlengine import schema
46
48
 
47
- table = schema.table_from_database("temp/chinook.db", "Invoice")
49
+ table = schema.table_from_database("temp/chinook.db", "Album")
48
50
  table
49
51
  ```
50
52
 
51
- <table style="border-collapse: collapse; font-size: 14px;"><caption style="font-size: 18px; font-weight: bold;">Invoice</caption><thead><tr><td style="border: 1px solid #555; text-align: center;">InvoiceId</td><td style="border: 1px solid #555; text-align: center;">CustomerId</td><td style="border: 1px solid #555; text-align: center;">InvoiceDate</td><td style="border: 1px solid #555; text-align: center;">BillingAddress</td><td style="border: 1px solid #555; text-align: center;">BillingCity</td><td style="border: 1px solid #555; text-align: center;">BillingState</td><td style="border: 1px solid #555; text-align: center;">BillingCountry</td><td style="border: 1px solid #555; text-align: center;">BillingPostalCode</td><td style="border: 1px solid #555; text-align: center;">Total</td></tr></thead><tbody><tr><td style="border: 1px solid #000; text-align: center;">1</td><td style="border: 1px solid #000; text-align: center;">2</td><td style="border: 1px solid #000; text-align: center;">2021-01-01 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Theodor-Heuss-Straße 34</td><td style="border: 1px solid #000; text-align: center;">Stuttgart</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Germany</td><td style="border: 1px solid #000; text-align: center;">70174</td><td style="border: 1px solid #000; text-align: center;">1.98</td></tr><tr><td style="border: 1px solid #000; text-align: center;">2</td><td style="border: 1px solid #000; text-align: center;">4</td><td style="border: 1px solid #000; text-align: center;">2021-01-02 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Ullevålsveien 14</td><td style="border: 1px solid #000; text-align: center;">Oslo</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Norway</td><td style="border: 1px solid #000; text-align: center;">0171</td><td style="border: 1px solid #000; text-align: center;">3.96</td></tr><tr><td style="border: 1px solid #000; text-align: center;">3</td><td style="border: 1px solid #000; text-align: center;">8</td><td style="border: 1px solid #000; text-align: center;">2021-01-03 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Grétrystraat 63</td><td style="border: 1px solid #000; text-align: center;">Brussels</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Belgium</td><td style="border: 1px solid #000; text-align: center;">1000</td><td style="border: 1px solid #000; text-align: center;">5.94</td></tr><tr><td style="border: 1px solid #000; text-align: center;">4</td><td style="border: 1px solid #000; text-align: center;">14</td><td style="border: 1px solid #000; text-align: center;">2021-01-06 00:00:00</td><td style="border: 1px solid #000; text-align: center;">8210 111 ST NW</td><td style="border: 1px solid #000; text-align: center;">Edmonton</td><td style="border: 1px solid #000; text-align: center;">AB</td><td style="border: 1px solid #000; text-align: center;">Canada</td><td style="border: 1px solid #000; text-align: center;">T6G 2C7</td><td style="border: 1px solid #000; text-align: center;">8.91</td></tr><tr><td style="border: 1px solid #000; text-align: center;">5</td><td style="border: 1px solid #000; text-align: center;">23</td><td style="border: 1px solid #000; text-align: center;">2021-01-11 00:00:00</td><td style="border: 1px solid #000; text-align: center;">69 Salem Street</td><td style="border: 1px solid #000; text-align: center;">Boston</td><td style="border: 1px solid #000; text-align: center;">MA</td><td style="border: 1px solid #000; text-align: center;">USA</td><td style="border: 1px solid #000; text-align: center;">2113</td><td style="border: 1px solid #000; text-align: center;">13.86</td></tr><tr><td style="border: 1px solid #000; text-align: center;">6</td><td style="border: 1px solid #000; text-align: center;">37</td><td style="border: 1px solid #000; text-align: center;">2021-01-19 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Berger Straße 10</td><td style="border: 1px solid #000; text-align: center;">Frankfurt</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Germany</td><td style="border: 1px solid #000; text-align: center;">60316</td><td style="border: 1px solid #000; text-align: center;">0.99</td></tr><tr><td style="border: 1px solid #000; text-align: center;">7</td><td style="border: 1px solid #000; text-align: center;">38</td><td style="border: 1px solid #000; text-align: center;">2021-02-01 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Barbarossastraße 19</td><td style="border: 1px solid #000; text-align: center;">Berlin</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Germany</td><td style="border: 1px solid #000; text-align: center;">10779</td><td style="border: 1px solid #000; text-align: center;">1.98</td></tr><tr><td style="border: 1px solid #000; text-align: center;">8</td><td style="border: 1px solid #000; text-align: center;">40</td><td style="border: 1px solid #000; text-align: center;">2021-02-01 00:00:00</td><td style="border: 1px solid #000; text-align: center;">8, Rue Hanovre</td><td style="border: 1px solid #000; text-align: center;">Paris</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">France</td><td style="border: 1px solid #000; text-align: center;">75002</td><td style="border: 1px solid #000; text-align: center;">1.98</td></tr><tr><td style="border: 1px solid #000; text-align: center;">9</td><td style="border: 1px solid #000; text-align: center;">42</td><td style="border: 1px solid #000; text-align: center;">2021-02-02 00:00:00</td><td style="border: 1px solid #000; text-align: center;">9, Place Louis Barthou</td><td style="border: 1px solid #000; text-align: center;">Bordeaux</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">France</td><td style="border: 1px solid #000; text-align: center;">33000</td><td style="border: 1px solid #000; text-align: center;">3.96</td></tr><tr><td style="border: 1px solid #000; text-align: center;">10</td><td style="border: 1px solid #000; text-align: center;">46</td><td style="border: 1px solid #000; text-align: center;">2021-02-03 00:00:00</td><td style="border: 1px solid #000; text-align: center;">3 Chatham Street</td><td style="border: 1px solid #000; text-align: center;">Dublin</td><td style="border: 1px solid #000; text-align: center;">Dublin</td><td style="border: 1px solid #000; text-align: center;">Ireland</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">5.94</td></tr><tr><td colspan="9" style="text-align:center;color:#888;font-style:italic;padding:8px;">... more rows ...</td></tr></tbody></table>
53
+ <table style="border-collapse: collapse; font-size: 14px;"><caption style="font-size: 18px; font-weight: bold;">Album</caption><thead><tr><td style="border: 1px solid #555; text-align: center;">AlbumId</td><td style="border: 1px solid #555; text-align: center;">Title</td><td style="border: 1px solid #555; text-align: center;">ArtistId</td></tr></thead><tbody><tr><td style="border: 1px solid #000; text-align: center;">1</td><td style="border: 1px solid #000; text-align: center;">For Those About To Rock We Salute You</td><td style="border: 1px solid #000; text-align: center;">1</td></tr><tr><td style="border: 1px solid #000; text-align: center;">2</td><td style="border: 1px solid #000; text-align: center;">Balls to the Wall</td><td style="border: 1px solid #000; text-align: center;">2</td></tr><tr><td style="border: 1px solid #000; text-align: center;">3</td><td style="border: 1px solid #000; text-align: center;">Restless and Wild</td><td style="border: 1px solid #000; text-align: center;">2</td></tr><tr><td style="border: 1px solid #000; text-align: center;">4</td><td style="border: 1px solid #000; text-align: center;">Let There Be Rock</td><td style="border: 1px solid #000; text-align: center;">1</td></tr><tr><td style="border: 1px solid #000; text-align: center;">5</td><td style="border: 1px solid #000; text-align: center;">Big Ones</td><td style="border: 1px solid #000; text-align: center;">3</td></tr><tr><td style="border: 1px solid #000; text-align: center;">6</td><td style="border: 1px solid #000; text-align: center;">Jagged Little Pill</td><td style="border: 1px solid #000; text-align: center;">4</td></tr><tr><td style="border: 1px solid #000; text-align: center;">7</td><td style="border: 1px solid #000; text-align: center;">Facelift</td><td style="border: 1px solid #000; text-align: center;">5</td></tr><tr><td style="border: 1px solid #000; text-align: center;">8</td><td style="border: 1px solid #000; text-align: center;">Warner 25 Anos</td><td style="border: 1px solid #000; text-align: center;">6</td></tr><tr><td style="border: 1px solid #000; text-align: center;">9</td><td style="border: 1px solid #000; text-align: center;">Plays Metallica By Four Cellos</td><td style="border: 1px solid #000; text-align: center;">7</td></tr><tr><td style="border: 1px solid #000; text-align: center;">10</td><td style="border: 1px solid #000; text-align: center;">Audioslave</td><td style="border: 1px solid #000; text-align: center;">8</td></tr><tr><td colspan="3" style="text-align:center;color:#888;font-style:italic;padding:8px;">... more rows ...</td></tr></tbody></table>
54
+
55
+ Note that GitHub's html rendering is simplified.
52
56
 
53
57
  ---
54
58
 
55
59
  You can preview select statements before fetching data to your variables.
56
60
 
57
61
  ```py
62
+ table = schema.table_from_database("temp/chinook.db", "Invoice")
63
+
58
64
  table.select("InvoiceId", "CustomerId", "BillingAddress", "BillingCountry", "Total")\
59
65
  .where\
60
66
  .gte("Total", 2.0)\
@@ -68,7 +74,7 @@ table.select("InvoiceId", "CustomerId", "BillingAddress", "BillingCountry", "Tot
68
74
 
69
75
  ## Purpose
70
76
 
71
- It's a tiny little modern ORM that lets you prototype your databases locally with great flexibility. Also, it can be used in production apps to store and retrieve data, because all select, update, delete queries are parametrized. But it does not restrict you from using your own queries which might not be paramerized with methods like `select.custom()` and `where.custom()`.
77
+ It's a tiny little modern ORM-like that lets you prototype your databases locally with great flexibility. Also, it can be used in production apps to store and retrieve data, because all select, update, delete queries are parametrized. But it does not restrict you from using your own queries which might not be paramerized with methods like `select.custom()` and `where.custom()`.
72
78
 
73
79
  And last (but not least) is data inspection. If you need to quickly inspect existing .db file but don't want to install yet another heavy ORM with a lot of unused dependencies, you might look into Sql-Engine, as it uses only native python modules.
74
80
 
@@ -119,27 +125,22 @@ __primary__ : list[str]
119
125
  More details at [Declaration](https://github.com/suffermuffin/SQL-Engine/blob/main/docs/table_declaration.md#table-declaration).
120
126
 
121
127
  ```py
122
- from sqlengine import SqlTableMixin
128
+ from sqlengine import SqlTableMixin, Primary
123
129
 
124
130
  # Helper constants for column names
125
- ID = "ID"
126
- Name = "Name"
131
+ ID = "ID"
132
+ Name = "Name"
127
133
  Occupation = "Occupation"
128
- Salary = "Salary"
134
+ Salary = "Salary"
129
135
 
130
136
 
131
137
  class Employees(SqlTableMixin):
132
138
 
133
- __columns__ = [ID, Name, Occupation, Salary]
134
- __types__ = [int, str, str, float]
135
- __primary__ = [ID]
139
+ ID : Primary[int]
140
+ Name : str | None
141
+ Occupation : str
142
+ Salary : float
136
143
 
137
- # You may overwrite your insert methods for type consistency
138
- def insert(self, id : int, name : str, occupation : str, salary : float) -> None:
139
- return super().insert(id, name, occupation, salary)
140
-
141
- def upsert(self, id : int, name : str, occupation : str, salary : float) -> None:
142
- return super().upsert(id, name, occupation, salary)
143
144
  ```
144
145
 
145
146
  ## Instantiation
@@ -149,8 +150,9 @@ More details at [Instantiation](https://github.com/suffermuffin/SQL-Engine/blob/
149
150
  ```py
150
151
  # Create an instance of the table class
151
152
  # with provided path to create or connect to
153
+ # `force_drop=True` to overwrite existing table if exists
152
154
 
153
- table = Employees("temp/data.db")
155
+ table = Employees("temp/data.db", force_drop=True)
154
156
  ```
155
157
 
156
158
  ## Row insertion
@@ -161,11 +163,16 @@ table = Employees("temp/data.db")
161
163
  table.insert(1, "John Doe", "Software Engineer", 75000.0)
162
164
  ```
163
165
 
166
+ ```py
167
+ # Use kwargs mapping to insert/upsert one row
168
+
169
+ table.insert(2, salary=80000.0, name="Jane Smith", occupation="Data Scientist")
170
+ ```
171
+
164
172
  ```py
165
173
  # Bulk insert multiple rows
166
174
 
167
175
  employees_data = [
168
- (2, "Jane Smith", "Data Scientist", 80000.0),
169
176
  (3, "Alice Johnson", "Product Manager", 90000.0),
170
177
  (4, "Bob Brown", "Project Manager", 78000.0),
171
178
  (5, "Charlie Davis", "UI/UX Designer", 65000.0),
@@ -267,6 +274,43 @@ table[4:10:2]
267
274
  # (8, 'Frank White', 'Quality Assurance', 53000.0)]
268
275
  ```
269
276
 
277
+ ## Custom Types
278
+
279
+ More details at [Custom Types](https://github.com/suffermuffin/SQL-Engine/blob/main/docs/custom_types.md).
280
+
281
+ ```py
282
+ from datetime import datetime
283
+ from sqlengine import SqlTableMixin, Primary
284
+
285
+ class DateTime(datetime):
286
+
287
+ def to_sql(self) -> str:
288
+ return self.strftime("%Y-%m-%d %H:%M")
289
+
290
+ @classmethod
291
+ def from_sql(cls, sql : bytes):
292
+ return cls.fromisoformat(sql.decode('utf-8'))
293
+
294
+ def __repr__(self):
295
+ return f"DateTime({self.time})"
296
+
297
+
298
+ class ReservationIndex(SqlTableMixin):
299
+
300
+ user_id : Primary[int]
301
+ room_id : Primary[str]
302
+ time_at : Primary[DateTime]
303
+ user_name : str | None
304
+
305
+
306
+ table = ReservationIndex("temp/data.db")
307
+
308
+ table.insert(1, "loft_1", DateTime.now(), None)
309
+ table
310
+ ```
311
+
312
+ <table style="border-collapse: collapse; font-size: 14px;"><caption style="font-size: 18px; font-weight: bold;">ReservationIndex</caption><thead><tr><td style="border: 1px solid #555; text-align: center;">user_id</td><td style="border: 1px solid #555; text-align: center;">room_id</td><td style="border: 1px solid #555; text-align: center;">time_at</td><td style="border: 1px solid #555; text-align: center;">user_name</td></tr></thead><tbody><tr><td style="border: 1px solid #000; text-align: center;">1</td><td style="border: 1px solid #000; text-align: center;">loft_1</td><td style="border: 1px solid #000; text-align: center;">2026-05-29 21:29:00</td><td style="border: 1px solid #000; text-align: center;">None</td></tr></tbody></table>
313
+
270
314
  ## Csv Converter
271
315
 
272
316
  ```py
@@ -278,10 +322,40 @@ to_csv(table, "temp/table.csv")
278
322
 
279
323
  ```py
280
324
  # Save query result to csv
281
-
282
325
  to_csv(table.select.where.gt(Salary, 70_000), "temp/query.csv")
283
326
  ```
284
327
 
328
+ ```py
329
+ # Stream to csv
330
+ with table.transaction():
331
+ to_csv(table, "temp/query.csv", stream_batch_size=1000)
332
+ ```
333
+
334
+ ## Pandas-like Converter
335
+
336
+ ```py
337
+ # via one shot
338
+ import pandas as pd
339
+ from sqlengine.utils import to_dicts
340
+
341
+ df = pd.DataFrame(to_dicts(table))
342
+ df.set_index("ID", inplace=True)
343
+ ```
344
+
345
+ ```py
346
+ # via generator
347
+ import pandas as pd
348
+ from sqlengine.utils import to_dicts_stream
349
+
350
+ df = pd.DataFrame(columns=table.columns)
351
+
352
+ with table.transaction():
353
+ for batch in to_dicts_stream(table, batch_size=1000):
354
+ df = pd.concat([df, pd.DataFrame(batch)], axis=0)
355
+
356
+ df.set_index("ID", inplace=True)
357
+ ```
358
+
285
359
  # Full Documentation
286
360
 
287
361
  For detailed usage, API reference, and advanced examples, see the [full documentation](https://github.com/suffermuffin/SQL-Engine/blob/main/docs/index.md).
@@ -13,7 +13,9 @@
13
13
  - [Delete Query](#delete-query)
14
14
  - [Transaction](#transaction)
15
15
  - [Get Item](#get-item)
16
+ - [Custom Types](#custom-types)
16
17
  - [Csv Converter](#csv-converter)
18
+ - [Pandas-like Converter](#pandas-like-converter)
17
19
  - [Full Documentation](#full-documentation)
18
20
 
19
21
 
@@ -32,17 +34,21 @@ Sql-Engine implements Jupyter integration and dynamic schema building. You can e
32
34
  ```py
33
35
  from sqlengine import schema
34
36
 
35
- table = schema.table_from_database("temp/chinook.db", "Invoice")
37
+ table = schema.table_from_database("temp/chinook.db", "Album")
36
38
  table
37
39
  ```
38
40
 
39
- <table style="border-collapse: collapse; font-size: 14px;"><caption style="font-size: 18px; font-weight: bold;">Invoice</caption><thead><tr><td style="border: 1px solid #555; text-align: center;">InvoiceId</td><td style="border: 1px solid #555; text-align: center;">CustomerId</td><td style="border: 1px solid #555; text-align: center;">InvoiceDate</td><td style="border: 1px solid #555; text-align: center;">BillingAddress</td><td style="border: 1px solid #555; text-align: center;">BillingCity</td><td style="border: 1px solid #555; text-align: center;">BillingState</td><td style="border: 1px solid #555; text-align: center;">BillingCountry</td><td style="border: 1px solid #555; text-align: center;">BillingPostalCode</td><td style="border: 1px solid #555; text-align: center;">Total</td></tr></thead><tbody><tr><td style="border: 1px solid #000; text-align: center;">1</td><td style="border: 1px solid #000; text-align: center;">2</td><td style="border: 1px solid #000; text-align: center;">2021-01-01 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Theodor-Heuss-Straße 34</td><td style="border: 1px solid #000; text-align: center;">Stuttgart</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Germany</td><td style="border: 1px solid #000; text-align: center;">70174</td><td style="border: 1px solid #000; text-align: center;">1.98</td></tr><tr><td style="border: 1px solid #000; text-align: center;">2</td><td style="border: 1px solid #000; text-align: center;">4</td><td style="border: 1px solid #000; text-align: center;">2021-01-02 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Ullevålsveien 14</td><td style="border: 1px solid #000; text-align: center;">Oslo</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Norway</td><td style="border: 1px solid #000; text-align: center;">0171</td><td style="border: 1px solid #000; text-align: center;">3.96</td></tr><tr><td style="border: 1px solid #000; text-align: center;">3</td><td style="border: 1px solid #000; text-align: center;">8</td><td style="border: 1px solid #000; text-align: center;">2021-01-03 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Grétrystraat 63</td><td style="border: 1px solid #000; text-align: center;">Brussels</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Belgium</td><td style="border: 1px solid #000; text-align: center;">1000</td><td style="border: 1px solid #000; text-align: center;">5.94</td></tr><tr><td style="border: 1px solid #000; text-align: center;">4</td><td style="border: 1px solid #000; text-align: center;">14</td><td style="border: 1px solid #000; text-align: center;">2021-01-06 00:00:00</td><td style="border: 1px solid #000; text-align: center;">8210 111 ST NW</td><td style="border: 1px solid #000; text-align: center;">Edmonton</td><td style="border: 1px solid #000; text-align: center;">AB</td><td style="border: 1px solid #000; text-align: center;">Canada</td><td style="border: 1px solid #000; text-align: center;">T6G 2C7</td><td style="border: 1px solid #000; text-align: center;">8.91</td></tr><tr><td style="border: 1px solid #000; text-align: center;">5</td><td style="border: 1px solid #000; text-align: center;">23</td><td style="border: 1px solid #000; text-align: center;">2021-01-11 00:00:00</td><td style="border: 1px solid #000; text-align: center;">69 Salem Street</td><td style="border: 1px solid #000; text-align: center;">Boston</td><td style="border: 1px solid #000; text-align: center;">MA</td><td style="border: 1px solid #000; text-align: center;">USA</td><td style="border: 1px solid #000; text-align: center;">2113</td><td style="border: 1px solid #000; text-align: center;">13.86</td></tr><tr><td style="border: 1px solid #000; text-align: center;">6</td><td style="border: 1px solid #000; text-align: center;">37</td><td style="border: 1px solid #000; text-align: center;">2021-01-19 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Berger Straße 10</td><td style="border: 1px solid #000; text-align: center;">Frankfurt</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Germany</td><td style="border: 1px solid #000; text-align: center;">60316</td><td style="border: 1px solid #000; text-align: center;">0.99</td></tr><tr><td style="border: 1px solid #000; text-align: center;">7</td><td style="border: 1px solid #000; text-align: center;">38</td><td style="border: 1px solid #000; text-align: center;">2021-02-01 00:00:00</td><td style="border: 1px solid #000; text-align: center;">Barbarossastraße 19</td><td style="border: 1px solid #000; text-align: center;">Berlin</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">Germany</td><td style="border: 1px solid #000; text-align: center;">10779</td><td style="border: 1px solid #000; text-align: center;">1.98</td></tr><tr><td style="border: 1px solid #000; text-align: center;">8</td><td style="border: 1px solid #000; text-align: center;">40</td><td style="border: 1px solid #000; text-align: center;">2021-02-01 00:00:00</td><td style="border: 1px solid #000; text-align: center;">8, Rue Hanovre</td><td style="border: 1px solid #000; text-align: center;">Paris</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">France</td><td style="border: 1px solid #000; text-align: center;">75002</td><td style="border: 1px solid #000; text-align: center;">1.98</td></tr><tr><td style="border: 1px solid #000; text-align: center;">9</td><td style="border: 1px solid #000; text-align: center;">42</td><td style="border: 1px solid #000; text-align: center;">2021-02-02 00:00:00</td><td style="border: 1px solid #000; text-align: center;">9, Place Louis Barthou</td><td style="border: 1px solid #000; text-align: center;">Bordeaux</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">France</td><td style="border: 1px solid #000; text-align: center;">33000</td><td style="border: 1px solid #000; text-align: center;">3.96</td></tr><tr><td style="border: 1px solid #000; text-align: center;">10</td><td style="border: 1px solid #000; text-align: center;">46</td><td style="border: 1px solid #000; text-align: center;">2021-02-03 00:00:00</td><td style="border: 1px solid #000; text-align: center;">3 Chatham Street</td><td style="border: 1px solid #000; text-align: center;">Dublin</td><td style="border: 1px solid #000; text-align: center;">Dublin</td><td style="border: 1px solid #000; text-align: center;">Ireland</td><td style="border: 1px solid #000; text-align: center;">None</td><td style="border: 1px solid #000; text-align: center;">5.94</td></tr><tr><td colspan="9" style="text-align:center;color:#888;font-style:italic;padding:8px;">... more rows ...</td></tr></tbody></table>
41
+ <table style="border-collapse: collapse; font-size: 14px;"><caption style="font-size: 18px; font-weight: bold;">Album</caption><thead><tr><td style="border: 1px solid #555; text-align: center;">AlbumId</td><td style="border: 1px solid #555; text-align: center;">Title</td><td style="border: 1px solid #555; text-align: center;">ArtistId</td></tr></thead><tbody><tr><td style="border: 1px solid #000; text-align: center;">1</td><td style="border: 1px solid #000; text-align: center;">For Those About To Rock We Salute You</td><td style="border: 1px solid #000; text-align: center;">1</td></tr><tr><td style="border: 1px solid #000; text-align: center;">2</td><td style="border: 1px solid #000; text-align: center;">Balls to the Wall</td><td style="border: 1px solid #000; text-align: center;">2</td></tr><tr><td style="border: 1px solid #000; text-align: center;">3</td><td style="border: 1px solid #000; text-align: center;">Restless and Wild</td><td style="border: 1px solid #000; text-align: center;">2</td></tr><tr><td style="border: 1px solid #000; text-align: center;">4</td><td style="border: 1px solid #000; text-align: center;">Let There Be Rock</td><td style="border: 1px solid #000; text-align: center;">1</td></tr><tr><td style="border: 1px solid #000; text-align: center;">5</td><td style="border: 1px solid #000; text-align: center;">Big Ones</td><td style="border: 1px solid #000; text-align: center;">3</td></tr><tr><td style="border: 1px solid #000; text-align: center;">6</td><td style="border: 1px solid #000; text-align: center;">Jagged Little Pill</td><td style="border: 1px solid #000; text-align: center;">4</td></tr><tr><td style="border: 1px solid #000; text-align: center;">7</td><td style="border: 1px solid #000; text-align: center;">Facelift</td><td style="border: 1px solid #000; text-align: center;">5</td></tr><tr><td style="border: 1px solid #000; text-align: center;">8</td><td style="border: 1px solid #000; text-align: center;">Warner 25 Anos</td><td style="border: 1px solid #000; text-align: center;">6</td></tr><tr><td style="border: 1px solid #000; text-align: center;">9</td><td style="border: 1px solid #000; text-align: center;">Plays Metallica By Four Cellos</td><td style="border: 1px solid #000; text-align: center;">7</td></tr><tr><td style="border: 1px solid #000; text-align: center;">10</td><td style="border: 1px solid #000; text-align: center;">Audioslave</td><td style="border: 1px solid #000; text-align: center;">8</td></tr><tr><td colspan="3" style="text-align:center;color:#888;font-style:italic;padding:8px;">... more rows ...</td></tr></tbody></table>
42
+
43
+ Note that GitHub's html rendering is simplified.
40
44
 
41
45
  ---
42
46
 
43
47
  You can preview select statements before fetching data to your variables.
44
48
 
45
49
  ```py
50
+ table = schema.table_from_database("temp/chinook.db", "Invoice")
51
+
46
52
  table.select("InvoiceId", "CustomerId", "BillingAddress", "BillingCountry", "Total")\
47
53
  .where\
48
54
  .gte("Total", 2.0)\
@@ -56,7 +62,7 @@ table.select("InvoiceId", "CustomerId", "BillingAddress", "BillingCountry", "Tot
56
62
 
57
63
  ## Purpose
58
64
 
59
- It's a tiny little modern ORM that lets you prototype your databases locally with great flexibility. Also, it can be used in production apps to store and retrieve data, because all select, update, delete queries are parametrized. But it does not restrict you from using your own queries which might not be paramerized with methods like `select.custom()` and `where.custom()`.
65
+ It's a tiny little modern ORM-like that lets you prototype your databases locally with great flexibility. Also, it can be used in production apps to store and retrieve data, because all select, update, delete queries are parametrized. But it does not restrict you from using your own queries which might not be paramerized with methods like `select.custom()` and `where.custom()`.
60
66
 
61
67
  And last (but not least) is data inspection. If you need to quickly inspect existing .db file but don't want to install yet another heavy ORM with a lot of unused dependencies, you might look into Sql-Engine, as it uses only native python modules.
62
68
 
@@ -107,27 +113,22 @@ __primary__ : list[str]
107
113
  More details at [Declaration](https://github.com/suffermuffin/SQL-Engine/blob/main/docs/table_declaration.md#table-declaration).
108
114
 
109
115
  ```py
110
- from sqlengine import SqlTableMixin
116
+ from sqlengine import SqlTableMixin, Primary
111
117
 
112
118
  # Helper constants for column names
113
- ID = "ID"
114
- Name = "Name"
119
+ ID = "ID"
120
+ Name = "Name"
115
121
  Occupation = "Occupation"
116
- Salary = "Salary"
122
+ Salary = "Salary"
117
123
 
118
124
 
119
125
  class Employees(SqlTableMixin):
120
126
 
121
- __columns__ = [ID, Name, Occupation, Salary]
122
- __types__ = [int, str, str, float]
123
- __primary__ = [ID]
127
+ ID : Primary[int]
128
+ Name : str | None
129
+ Occupation : str
130
+ Salary : float
124
131
 
125
- # You may overwrite your insert methods for type consistency
126
- def insert(self, id : int, name : str, occupation : str, salary : float) -> None:
127
- return super().insert(id, name, occupation, salary)
128
-
129
- def upsert(self, id : int, name : str, occupation : str, salary : float) -> None:
130
- return super().upsert(id, name, occupation, salary)
131
132
  ```
132
133
 
133
134
  ## Instantiation
@@ -137,8 +138,9 @@ More details at [Instantiation](https://github.com/suffermuffin/SQL-Engine/blob/
137
138
  ```py
138
139
  # Create an instance of the table class
139
140
  # with provided path to create or connect to
141
+ # `force_drop=True` to overwrite existing table if exists
140
142
 
141
- table = Employees("temp/data.db")
143
+ table = Employees("temp/data.db", force_drop=True)
142
144
  ```
143
145
 
144
146
  ## Row insertion
@@ -149,11 +151,16 @@ table = Employees("temp/data.db")
149
151
  table.insert(1, "John Doe", "Software Engineer", 75000.0)
150
152
  ```
151
153
 
154
+ ```py
155
+ # Use kwargs mapping to insert/upsert one row
156
+
157
+ table.insert(2, salary=80000.0, name="Jane Smith", occupation="Data Scientist")
158
+ ```
159
+
152
160
  ```py
153
161
  # Bulk insert multiple rows
154
162
 
155
163
  employees_data = [
156
- (2, "Jane Smith", "Data Scientist", 80000.0),
157
164
  (3, "Alice Johnson", "Product Manager", 90000.0),
158
165
  (4, "Bob Brown", "Project Manager", 78000.0),
159
166
  (5, "Charlie Davis", "UI/UX Designer", 65000.0),
@@ -255,6 +262,43 @@ table[4:10:2]
255
262
  # (8, 'Frank White', 'Quality Assurance', 53000.0)]
256
263
  ```
257
264
 
265
+ ## Custom Types
266
+
267
+ More details at [Custom Types](https://github.com/suffermuffin/SQL-Engine/blob/main/docs/custom_types.md).
268
+
269
+ ```py
270
+ from datetime import datetime
271
+ from sqlengine import SqlTableMixin, Primary
272
+
273
+ class DateTime(datetime):
274
+
275
+ def to_sql(self) -> str:
276
+ return self.strftime("%Y-%m-%d %H:%M")
277
+
278
+ @classmethod
279
+ def from_sql(cls, sql : bytes):
280
+ return cls.fromisoformat(sql.decode('utf-8'))
281
+
282
+ def __repr__(self):
283
+ return f"DateTime({self.time})"
284
+
285
+
286
+ class ReservationIndex(SqlTableMixin):
287
+
288
+ user_id : Primary[int]
289
+ room_id : Primary[str]
290
+ time_at : Primary[DateTime]
291
+ user_name : str | None
292
+
293
+
294
+ table = ReservationIndex("temp/data.db")
295
+
296
+ table.insert(1, "loft_1", DateTime.now(), None)
297
+ table
298
+ ```
299
+
300
+ <table style="border-collapse: collapse; font-size: 14px;"><caption style="font-size: 18px; font-weight: bold;">ReservationIndex</caption><thead><tr><td style="border: 1px solid #555; text-align: center;">user_id</td><td style="border: 1px solid #555; text-align: center;">room_id</td><td style="border: 1px solid #555; text-align: center;">time_at</td><td style="border: 1px solid #555; text-align: center;">user_name</td></tr></thead><tbody><tr><td style="border: 1px solid #000; text-align: center;">1</td><td style="border: 1px solid #000; text-align: center;">loft_1</td><td style="border: 1px solid #000; text-align: center;">2026-05-29 21:29:00</td><td style="border: 1px solid #000; text-align: center;">None</td></tr></tbody></table>
301
+
258
302
  ## Csv Converter
259
303
 
260
304
  ```py
@@ -266,10 +310,40 @@ to_csv(table, "temp/table.csv")
266
310
 
267
311
  ```py
268
312
  # Save query result to csv
269
-
270
313
  to_csv(table.select.where.gt(Salary, 70_000), "temp/query.csv")
271
314
  ```
272
315
 
316
+ ```py
317
+ # Stream to csv
318
+ with table.transaction():
319
+ to_csv(table, "temp/query.csv", stream_batch_size=1000)
320
+ ```
321
+
322
+ ## Pandas-like Converter
323
+
324
+ ```py
325
+ # via one shot
326
+ import pandas as pd
327
+ from sqlengine.utils import to_dicts
328
+
329
+ df = pd.DataFrame(to_dicts(table))
330
+ df.set_index("ID", inplace=True)
331
+ ```
332
+
333
+ ```py
334
+ # via generator
335
+ import pandas as pd
336
+ from sqlengine.utils import to_dicts_stream
337
+
338
+ df = pd.DataFrame(columns=table.columns)
339
+
340
+ with table.transaction():
341
+ for batch in to_dicts_stream(table, batch_size=1000):
342
+ df = pd.concat([df, pd.DataFrame(batch)], axis=0)
343
+
344
+ df.set_index("ID", inplace=True)
345
+ ```
346
+
273
347
  # Full Documentation
274
348
 
275
349
  For detailed usage, API reference, and advanced examples, see the [full documentation](https://github.com/suffermuffin/SQL-Engine/blob/main/docs/index.md).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlengine-lite"
3
- version = "2.1.1"
3
+ version = "2.2.1"
4
4
  description = "Cute sqlite3 wrapper for sql tables"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -19,7 +19,13 @@ where = ["src"]
19
19
  requires = ["setuptools>=61.0"]
20
20
  build-backend = "setuptools.build_meta"
21
21
 
22
+ [dependency-groups]
23
+ dev = [
24
+ "black==26.5.1",
25
+ "pydoc-markdown>=4.8.2",
26
+ ]
27
+
22
28
  [project.urls]
23
29
  Homepage = "https://github.com/suffermuffin/SQL-Engine"
24
30
  Repository = "https://github.com/suffermuffin/SQL-Engine.git"
25
- Documentation = "https://github.com/suffermuffin/SQL-Engine/blob/main/docs/index.md"
31
+ Documentation = "https://github.com/suffermuffin/SQL-Engine/blob/main/docs/index.md"
@@ -0,0 +1,11 @@
1
+ from ._internal import sqlgen
2
+ from ._internal import ConnectionManager
3
+ from ._internal.types import Schema, Primary, register_type
4
+ from .sqltable import SqlTableMixin
5
+
6
+ __author__ = "suffermuffin"
7
+
8
+ __all__ = [
9
+ "sqlgen", "Schema", "SqlTableMixin",
10
+ "Primary", "ConnectionManager", "register_type"
11
+ ]
@@ -0,0 +1,4 @@
1
+ from .statements import Select, Delete, Update
2
+ from .connection_manager import ConnectionManager
3
+
4
+ __all__ = ["Select", "Delete", "Update", "ConnectionManager"]