async-easy-model 0.1.12__py3-none-any.whl → 0.2.1__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.
- async_easy_model/auto_relationships.py +12 -0
- async_easy_model/model.py +635 -84
- async_easy_model-0.2.1.dist-info/METADATA +343 -0
- async_easy_model-0.2.1.dist-info/RECORD +10 -0
- async_easy_model-0.1.12.dist-info/METADATA +0 -533
- async_easy_model-0.1.12.dist-info/RECORD +0 -10
- {async_easy_model-0.1.12.dist-info → async_easy_model-0.2.1.dist-info}/LICENSE +0 -0
- {async_easy_model-0.1.12.dist-info → async_easy_model-0.2.1.dist-info}/WHEEL +0 -0
- {async_easy_model-0.1.12.dist-info → async_easy_model-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,343 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: async-easy-model
|
3
|
+
Version: 0.2.1
|
4
|
+
Summary: A simplified SQLModel-based ORM for async database operations
|
5
|
+
Home-page: https://github.com/puntorigen/easy-model
|
6
|
+
Author: Pablo Schaffner
|
7
|
+
Author-email: pablo@puntorigen.com
|
8
|
+
Keywords: orm,sqlmodel,database,async,postgresql,sqlite
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
10
|
+
Classifier: Intended Audience :: Developers
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Requires-Python: >=3.7
|
20
|
+
Description-Content-Type: text/markdown
|
21
|
+
License-File: LICENSE
|
22
|
+
Requires-Dist: sqlmodel>=0.0.8
|
23
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
24
|
+
Requires-Dist: asyncpg>=0.25.0
|
25
|
+
Requires-Dist: aiosqlite>=0.19.0
|
26
|
+
Requires-Dist: greenlet>=3.1.1
|
27
|
+
Requires-Dist: inflection>=0.5.1
|
28
|
+
Dynamic: author
|
29
|
+
Dynamic: author-email
|
30
|
+
Dynamic: classifier
|
31
|
+
Dynamic: description
|
32
|
+
Dynamic: description-content-type
|
33
|
+
Dynamic: home-page
|
34
|
+
Dynamic: keywords
|
35
|
+
Dynamic: requires-dist
|
36
|
+
Dynamic: requires-python
|
37
|
+
Dynamic: summary
|
38
|
+
|
39
|
+
# EasyModel
|
40
|
+
|
41
|
+
A simplified SQLModel-based ORM for async database operations in Python. EasyModel provides a clean and intuitive interface for common database operations while leveraging the power of SQLModel and SQLAlchemy.
|
42
|
+
|
43
|
+
## Features
|
44
|
+
|
45
|
+
- Easy-to-use async database operations with standardized methods
|
46
|
+
- Intuitive APIs with sensible defaults (relationships loaded by default)
|
47
|
+
- Dictionary-based CRUD operations (select, insert, update, delete)
|
48
|
+
- Built on top of SQLModel and SQLAlchemy
|
49
|
+
- Support for both PostgreSQL and SQLite databases
|
50
|
+
- Type hints for better IDE support
|
51
|
+
- Automatic `id`, `created_at` and `updated_at` fields provided by default
|
52
|
+
- Enhanced relationship handling with eager loading and nested operations
|
53
|
+
- Flexible ordering of query results with support for relationship fields
|
54
|
+
- Automatic relationship detection
|
55
|
+
- Automatic schema migrations for evolving database models
|
56
|
+
|
57
|
+
## Installation
|
58
|
+
|
59
|
+
```bash
|
60
|
+
pip install async-easy-model
|
61
|
+
```
|
62
|
+
|
63
|
+
## Quick Start with Standardized API
|
64
|
+
|
65
|
+
This section demonstrates the preferred usage of EasyModel with its standardized API methods.
|
66
|
+
|
67
|
+
```python
|
68
|
+
from async_easy_model import EasyModel, init_db, db_config, Field
|
69
|
+
from typing import Optional
|
70
|
+
from datetime import datetime
|
71
|
+
|
72
|
+
# Configure your database
|
73
|
+
db_config.configure_sqlite("database.db")
|
74
|
+
|
75
|
+
# Define your model
|
76
|
+
class User(EasyModel, table=True):
|
77
|
+
# id field is automatically created (primary key)
|
78
|
+
username: str = Field(unique=True)
|
79
|
+
email: str
|
80
|
+
is_active: bool = Field(default=True)
|
81
|
+
# created_at and updated_at fields are automatically included
|
82
|
+
|
83
|
+
class Post(EasyModel, table=True):
|
84
|
+
# id field is automatically created (primary key)
|
85
|
+
title: str
|
86
|
+
content: str
|
87
|
+
user_id: Optional[int] = Field(default=None, foreign_key="user.id")
|
88
|
+
# created_at and updated_at fields are automatically included
|
89
|
+
|
90
|
+
# Initialize your database
|
91
|
+
async def setup():
|
92
|
+
await init_db()
|
93
|
+
|
94
|
+
# Use the standardized methods in your code
|
95
|
+
async def main():
|
96
|
+
# Insert a new user
|
97
|
+
user = await User.insert({
|
98
|
+
"username": "john_doe",
|
99
|
+
"email": "john@example.com"
|
100
|
+
})
|
101
|
+
|
102
|
+
# Insert with relationships
|
103
|
+
post = await Post.insert({
|
104
|
+
"title": "My First Post",
|
105
|
+
"content": "Hello world!",
|
106
|
+
"user": {"username": "john_doe"} # Will find the user by username
|
107
|
+
})
|
108
|
+
|
109
|
+
# Select with criteria
|
110
|
+
active_users = await User.select({"is_active": True}, all=True)
|
111
|
+
|
112
|
+
# Select with wildcard search
|
113
|
+
gmail_users = await User.select({"email": "*@gmail.com"}, all=True)
|
114
|
+
|
115
|
+
# Select with ordering and limit
|
116
|
+
recent_posts = await Post.select({}, order_by="-id", limit=5)
|
117
|
+
# Note: limit > 1 automatically sets all=True
|
118
|
+
|
119
|
+
# Update by criteria
|
120
|
+
await User.update(
|
121
|
+
{"is_active": False},
|
122
|
+
{"last_login": None} # Update users with no login
|
123
|
+
)
|
124
|
+
|
125
|
+
# Delete with criteria
|
126
|
+
await Post.delete({"user": {"username": "john_doe"}})
|
127
|
+
```
|
128
|
+
|
129
|
+
## Standardized API Methods
|
130
|
+
|
131
|
+
EasyModel provides a set of standardized methods that make it easy and intuitive to perform common database operations.
|
132
|
+
|
133
|
+
### Select Method
|
134
|
+
|
135
|
+
The `select()` method is a powerful and flexible way to query data:
|
136
|
+
|
137
|
+
```python
|
138
|
+
# Get active users
|
139
|
+
active_users = await User.select({"is_active": True}, all=True)
|
140
|
+
|
141
|
+
# Get single user by username (returns first match)
|
142
|
+
user = await User.select({"username": "john_doe"})
|
143
|
+
|
144
|
+
# Explicitly get first result
|
145
|
+
first_admin = await User.select({"role": "admin"}, first=True)
|
146
|
+
|
147
|
+
# With wildcard pattern matching
|
148
|
+
gmail_users = await User.select({"email": "*@gmail.com"}, all=True)
|
149
|
+
|
150
|
+
# With ordering and limit (automatically sets all=True)
|
151
|
+
newest_users = await User.select({}, order_by="-created_at", limit=5)
|
152
|
+
|
153
|
+
# With ordering by multiple fields
|
154
|
+
sorted_users = await User.select({}, order_by=["last_name", "first_name"], all=True)
|
155
|
+
|
156
|
+
# With ordering by nested relationship fields using dot notation
|
157
|
+
books_by_author = await Book.select({}, order_by="author.name", all=True)
|
158
|
+
posts_by_popularity = await Post.select({}, order_by=["-comments.count", "title"], all=True)
|
159
|
+
```
|
160
|
+
|
161
|
+
### Insert Method
|
162
|
+
|
163
|
+
The `insert()` method supports both single and multiple records with relationship handling and returns the newly created records with assigned IDs and auto-generated fields (`created_at`, `updated_at`):
|
164
|
+
|
165
|
+
```python
|
166
|
+
# Insert single record
|
167
|
+
user = await User.insert({
|
168
|
+
"username": "john_doe",
|
169
|
+
"email": "john@example.com"
|
170
|
+
})
|
171
|
+
print(user.id) # Newly assigned ID is available
|
172
|
+
print(user.created_at) # Auto-generated timestamp is available
|
173
|
+
|
174
|
+
# Insert with relationship
|
175
|
+
comment = await Comment.insert({
|
176
|
+
"text": "Great post!",
|
177
|
+
"post": {"id": 1}, # Link by ID
|
178
|
+
"author": {"username": "jane_doe"} # Link by attribute lookup
|
179
|
+
})
|
180
|
+
|
181
|
+
# Insert multiple records
|
182
|
+
products = await Product.insert([
|
183
|
+
{"name": "Product 1", "price": 10.99},
|
184
|
+
{"name": "Product 2", "price": 24.99}
|
185
|
+
])
|
186
|
+
```
|
187
|
+
|
188
|
+
### Update Method
|
189
|
+
|
190
|
+
The `update()` method allows updates based on ID or criteria:
|
191
|
+
|
192
|
+
```python
|
193
|
+
# Update by ID
|
194
|
+
user = await User.update({"email": "new@example.com"}, 1)
|
195
|
+
|
196
|
+
# Update by criteria
|
197
|
+
count = await User.update(
|
198
|
+
{"is_active": False},
|
199
|
+
{"last_login": None} # Set all users without login to inactive
|
200
|
+
)
|
201
|
+
|
202
|
+
# Update with relationships
|
203
|
+
await User.update(
|
204
|
+
{"department": {"name": "Sales"}}, # Update department relationship
|
205
|
+
{"username": "john_doe"}
|
206
|
+
)
|
207
|
+
```
|
208
|
+
|
209
|
+
### Delete Method
|
210
|
+
|
211
|
+
The `delete()` method provides a consistent way to delete records:
|
212
|
+
|
213
|
+
```python
|
214
|
+
# Delete by ID
|
215
|
+
success = await User.delete(1)
|
216
|
+
|
217
|
+
# Delete by criteria
|
218
|
+
deleted_count = await User.delete({"is_active": False})
|
219
|
+
|
220
|
+
# Delete with compound criteria
|
221
|
+
await Post.delete({"author": {"username": "john_doe"}, "is_published": False})
|
222
|
+
```
|
223
|
+
|
224
|
+
## Convenience Query Methods
|
225
|
+
|
226
|
+
EasyModel also provides convenient shorthand methods for common queries:
|
227
|
+
|
228
|
+
```python
|
229
|
+
# Get all records with relationships loaded (default)
|
230
|
+
users = await User.all()
|
231
|
+
|
232
|
+
# Get all records ordered by a field
|
233
|
+
users = await User.all(order_by="username")
|
234
|
+
|
235
|
+
# Get the first record
|
236
|
+
user = await User.first()
|
237
|
+
|
238
|
+
# Get the most recently created user
|
239
|
+
newest_user = await User.first(order_by="-created_at")
|
240
|
+
|
241
|
+
# Get limited records
|
242
|
+
recent_users = await User.limit(10, order_by="-created_at")
|
243
|
+
```
|
244
|
+
|
245
|
+
## Automatic Relationship Detection
|
246
|
+
|
247
|
+
EasyModel supports automatic relationship detection based on foreign key fields:
|
248
|
+
|
249
|
+
```python
|
250
|
+
from async_easy_model import enable_auto_relationships, EasyModel, init_db, Field
|
251
|
+
from typing import Optional
|
252
|
+
|
253
|
+
# Enable automatic relationship detection
|
254
|
+
enable_auto_relationships()
|
255
|
+
|
256
|
+
# Define models with foreign keys but without explicit relationships
|
257
|
+
class Author(EasyModel, table=True):
|
258
|
+
# id field is automatically created (primary key)
|
259
|
+
name: str
|
260
|
+
|
261
|
+
class Book(EasyModel, table=True):
|
262
|
+
title: str
|
263
|
+
author_id: Optional[int] = Field(default=None, foreign_key="author.id")
|
264
|
+
# No need to define Relationship attributes - they're detected automatically!
|
265
|
+
|
266
|
+
# Use the automatically detected relationships
|
267
|
+
async def main():
|
268
|
+
await init_db()
|
269
|
+
author = await Author.insert({"name": "Jane Author"})
|
270
|
+
|
271
|
+
book = await Book.insert({
|
272
|
+
"title": "Auto-detected Relationships",
|
273
|
+
"author_id": author.id
|
274
|
+
})
|
275
|
+
|
276
|
+
# Show the book with its author
|
277
|
+
print(f"Book: {book.title}, Author: {book.author.name}")
|
278
|
+
```
|
279
|
+
|
280
|
+
### Another Example
|
281
|
+
```python
|
282
|
+
# Using the standard insert with nested dictionaries (recommended)
|
283
|
+
new_book = await Book.insert({
|
284
|
+
"title": "New Book",
|
285
|
+
"author": {"name": "Jane Author"} # Will create or find the author
|
286
|
+
})
|
287
|
+
```
|
288
|
+
|
289
|
+
## Automatic Schema Migrations
|
290
|
+
|
291
|
+
EasyModel includes automatic database migration capabilities, similar to alembic:
|
292
|
+
|
293
|
+
```python
|
294
|
+
from async_easy_model import MigrationManager
|
295
|
+
|
296
|
+
async def apply_migrations():
|
297
|
+
migration_manager = MigrationManager()
|
298
|
+
results = await migration_manager.migrate_models([User, Post])
|
299
|
+
|
300
|
+
if results:
|
301
|
+
print("Migrations applied:")
|
302
|
+
for model_name, changes in results.items():
|
303
|
+
print(f" {model_name}:")
|
304
|
+
for change in changes:
|
305
|
+
print(f" - {change}")
|
306
|
+
```
|
307
|
+
|
308
|
+
## Legacy API Methods
|
309
|
+
|
310
|
+
The following methods are still supported but the standardized methods above are recommended for new code:
|
311
|
+
|
312
|
+
### Traditional CRUD Operations
|
313
|
+
|
314
|
+
```python
|
315
|
+
# Create a record (consider using insert() instead)
|
316
|
+
user = User(username="john_doe", email="john@example.com")
|
317
|
+
await user.save()
|
318
|
+
|
319
|
+
# Get by ID (consider using select() instead)
|
320
|
+
user = await User.get_by_id(1)
|
321
|
+
|
322
|
+
# Get by attribute (consider using select() instead)
|
323
|
+
users = await User.get_by_attribute(is_active=True, all=True)
|
324
|
+
|
325
|
+
# Update by ID (consider using update() instead)
|
326
|
+
updated_user = await User.update_by_id(1, {"email": "new_email@example.com"})
|
327
|
+
|
328
|
+
# Update by attribute (consider using update() instead)
|
329
|
+
await User.update_by_attribute(
|
330
|
+
{"is_active": False}, # Update data
|
331
|
+
is_active=True, role="guest" # Filter criteria
|
332
|
+
)
|
333
|
+
|
334
|
+
# Delete by ID (consider using delete() instead)
|
335
|
+
success = await User.delete_by_id(1)
|
336
|
+
|
337
|
+
# Delete by attribute (consider using delete() instead)
|
338
|
+
deleted_count = await User.delete_by_attribute(is_active=False)
|
339
|
+
```
|
340
|
+
|
341
|
+
## Complete Documentation
|
342
|
+
|
343
|
+
For complete documentation, including advanced features, please see the [full documentation](DOCS.md).
|
@@ -0,0 +1,10 @@
|
|
1
|
+
async_easy_model/__init__.py,sha256=JcNQU_W8DUFnGVoOkbwfLDz5akT6jjMNVYLzWy_HA6w,1643
|
2
|
+
async_easy_model/auto_relationships.py,sha256=VetxcrOdKGGSImTiysRFR8PSOSlo50RqnVG95CLe8Jg,22433
|
3
|
+
async_easy_model/migrations.py,sha256=rYDGCGlruSugAmPfdIF2-uhyG6UvC_2qbF3BXJ084qI,17803
|
4
|
+
async_easy_model/model.py,sha256=D8nXgiYGu1WdNGciVSESyH4hmYmEx2_Rs6pBQQpyEA8,52958
|
5
|
+
async_easy_model/relationships.py,sha256=vR5BsJpGaDcecCcNlg9-ouZfxFXFQv5kOyiXhKp_T7A,3286
|
6
|
+
async_easy_model-0.2.1.dist-info/LICENSE,sha256=uwDkl6oHbRltW7vYKNc4doJyhtwhyrSNFFlPpKATwLE,1072
|
7
|
+
async_easy_model-0.2.1.dist-info/METADATA,sha256=RrlZpVzUvAO8RWPMH7g9w2DUKSyjsXqxfKsPzaOs6QM,10420
|
8
|
+
async_easy_model-0.2.1.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
9
|
+
async_easy_model-0.2.1.dist-info/top_level.txt,sha256=e5_47sGmJnyxz2msfwU6C316EqmrSd9RGIYwZyWx68E,17
|
10
|
+
async_easy_model-0.2.1.dist-info/RECORD,,
|