async-easy-model 0.1.12__py3-none-any.whl → 0.2.2__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/__init__.py +1 -1
- async_easy_model/auto_relationships.py +12 -0
- async_easy_model/model.py +652 -101
- async_easy_model-0.2.2.dist-info/METADATA +332 -0
- async_easy_model-0.2.2.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.2.dist-info}/LICENSE +0 -0
- {async_easy_model-0.1.12.dist-info → async_easy_model-0.2.2.dist-info}/WHEEL +0 -0
- {async_easy_model-0.1.12.dist-info → async_easy_model-0.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,332 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: async-easy-model
|
3
|
+
Version: 0.2.2
|
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
|
+
# async-easy-model
|
40
|
+
|
41
|
+
A simplified SQLModel-based ORM for async database operations in Python. async-easy-model provides a clean, intuitive interface for common database operations while leveraging the power of SQLModel and SQLAlchemy.
|
42
|
+
|
43
|
+
<p align="center">
|
44
|
+
<img src="https://img.shields.io/pypi/v/async-easy-model" alt="PyPI Version">
|
45
|
+
<img src="https://img.shields.io/pypi/pyversions/async-easy-model" alt="Python Versions">
|
46
|
+
<img src="https://img.shields.io/github/license/puntorigen/easy_model" alt="License">
|
47
|
+
</p>
|
48
|
+
|
49
|
+
## Features
|
50
|
+
|
51
|
+
- 🚀 Easy-to-use async database operations with standardized methods
|
52
|
+
- 🔄 Intuitive APIs with sensible defaults for rapid development
|
53
|
+
- 📊 Dictionary-based CRUD operations (select, insert, update, delete)
|
54
|
+
- 🔗 Enhanced relationship handling with eager loading and nested operations
|
55
|
+
- 🔍 Powerful query methods with flexible ordering support
|
56
|
+
- ⚙️ Automatic relationship detection and bidirectional setup
|
57
|
+
- 📱 Support for both PostgreSQL and SQLite databases
|
58
|
+
- 🛠️ Built on top of SQLModel and SQLAlchemy for robust performance
|
59
|
+
- 📝 Type hints for better IDE support
|
60
|
+
- 🕒 Automatic `id`, `created_at` and `updated_at` fields provided by default
|
61
|
+
- 🔄 Automatic schema migrations for evolving database models
|
62
|
+
|
63
|
+
## Installation
|
64
|
+
|
65
|
+
```bash
|
66
|
+
pip install async-easy-model
|
67
|
+
```
|
68
|
+
|
69
|
+
## Basic Usage
|
70
|
+
|
71
|
+
```python
|
72
|
+
from async_easy_model import EasyModel, init_db, db_config, Field
|
73
|
+
from typing import Optional
|
74
|
+
from datetime import datetime
|
75
|
+
|
76
|
+
# Configure your database
|
77
|
+
db_config.configure_sqlite("database.db")
|
78
|
+
|
79
|
+
# Define your model
|
80
|
+
class User(EasyModel, table=True):
|
81
|
+
#no need to specify id, created_at or updated_at since EasyModel provides them by default
|
82
|
+
username: str = Field(unique=True)
|
83
|
+
email: str
|
84
|
+
|
85
|
+
# Initialize your database
|
86
|
+
async def setup():
|
87
|
+
await init_db()
|
88
|
+
|
89
|
+
# Use it in your async code
|
90
|
+
async def main():
|
91
|
+
await setup()
|
92
|
+
# Create a new user
|
93
|
+
user = await User.insert({
|
94
|
+
"username": "john_doe",
|
95
|
+
"email": "john@example.com"
|
96
|
+
})
|
97
|
+
|
98
|
+
# Get user ID
|
99
|
+
print(f"New user id: {user.id}")
|
100
|
+
```
|
101
|
+
|
102
|
+
## CRUD Operations
|
103
|
+
|
104
|
+
First, let's define some models that we'll use throughout the examples:
|
105
|
+
|
106
|
+
```python
|
107
|
+
from async_easy_model import EasyModel, Field
|
108
|
+
from typing import Optional, List
|
109
|
+
from datetime import datetime
|
110
|
+
|
111
|
+
class User(EasyModel, table=True):
|
112
|
+
username: str = Field(unique=True)
|
113
|
+
email: str
|
114
|
+
is_active: bool = Field(default=True)
|
115
|
+
|
116
|
+
class Post(EasyModel, table=True):
|
117
|
+
title: str
|
118
|
+
content: str
|
119
|
+
user_id: Optional[int] = Field(default=None, foreign_key="user.id")
|
120
|
+
|
121
|
+
class Comment(EasyModel, table=True):
|
122
|
+
text: str
|
123
|
+
post_id: Optional[int] = Field(default=None, foreign_key="post.id")
|
124
|
+
user_id: Optional[int] = Field(default=None, foreign_key="user.id")
|
125
|
+
|
126
|
+
class Department(EasyModel, table=True):
|
127
|
+
name: str = Field(unique=True)
|
128
|
+
|
129
|
+
class Product(EasyModel, table=True):
|
130
|
+
name: str
|
131
|
+
price: float
|
132
|
+
sales: int = Field(default=0)
|
133
|
+
|
134
|
+
class Book(EasyModel, table=True):
|
135
|
+
title: str
|
136
|
+
author_id: Optional[int] = Field(default=None, foreign_key="author.id")
|
137
|
+
|
138
|
+
class Author(EasyModel, table=True):
|
139
|
+
name: str
|
140
|
+
```
|
141
|
+
|
142
|
+
### Create (Insert)
|
143
|
+
|
144
|
+
```python
|
145
|
+
# Insert a single record
|
146
|
+
user = await User.insert({
|
147
|
+
"username": "john_doe",
|
148
|
+
"email": "john@example.com"
|
149
|
+
})
|
150
|
+
|
151
|
+
# Insert multiple records
|
152
|
+
users = await User.insert([
|
153
|
+
{"username": "user1", "email": "user1@example.com"},
|
154
|
+
{"username": "user2", "email": "user2@example.com"}
|
155
|
+
])
|
156
|
+
|
157
|
+
# Insert with relationships using nested dictionaries
|
158
|
+
post = await Post.insert({
|
159
|
+
"title": "My First Post",
|
160
|
+
"content": "Hello world!",
|
161
|
+
"user": {"username": "john_doe"} # Will automatically link to existing user
|
162
|
+
})
|
163
|
+
```
|
164
|
+
|
165
|
+
### Read (Retrieve)
|
166
|
+
|
167
|
+
```python
|
168
|
+
# Select by ID
|
169
|
+
user = await User.select({"id": 1})
|
170
|
+
|
171
|
+
# Select with criteria
|
172
|
+
users = await User.select({"is_active": True}, all=True)
|
173
|
+
|
174
|
+
# Select first matching record
|
175
|
+
first_user = await User.select({"is_active": True}, first=True)
|
176
|
+
|
177
|
+
# Select all records
|
178
|
+
all_users = await User.select({}, all=True)
|
179
|
+
|
180
|
+
# Select with wildcard pattern matching
|
181
|
+
gmail_users = await User.select({"email": "*@gmail.com"}, all=True)
|
182
|
+
|
183
|
+
# Select with ordering
|
184
|
+
recent_users = await User.select({}, order_by="-created_at", all=True)
|
185
|
+
|
186
|
+
# Select with limit
|
187
|
+
latest_posts = await Post.select({}, order_by="-created_at", limit=5)
|
188
|
+
# Note: limit > 1 automatically sets all=True
|
189
|
+
|
190
|
+
# Select with multiple ordering fields
|
191
|
+
sorted_users = await User.select({}, order_by=["last_name", "first_name"], all=True)
|
192
|
+
|
193
|
+
# Select with relationship ordering
|
194
|
+
posts_by_author = await Post.select({}, order_by="user.username", all=True)
|
195
|
+
```
|
196
|
+
|
197
|
+
### Update
|
198
|
+
|
199
|
+
```python
|
200
|
+
# Update by ID
|
201
|
+
user = await User.update({"is_active": False}, 1)
|
202
|
+
|
203
|
+
# Update by criteria
|
204
|
+
count = await User.update(
|
205
|
+
{"is_active": False},
|
206
|
+
{"last_login": None} # Set all users without login to inactive
|
207
|
+
)
|
208
|
+
|
209
|
+
# Update with relationships
|
210
|
+
await User.update(
|
211
|
+
{"department": {"name": "Sales"}}, # Update department relationship
|
212
|
+
{"username": "john_doe"}
|
213
|
+
)
|
214
|
+
```
|
215
|
+
|
216
|
+
### Delete
|
217
|
+
|
218
|
+
```python
|
219
|
+
# Delete by ID
|
220
|
+
success = await User.delete(1)
|
221
|
+
|
222
|
+
# Delete by criteria
|
223
|
+
deleted_count = await User.delete({"is_active": False})
|
224
|
+
|
225
|
+
# Delete with compound criteria
|
226
|
+
await Post.delete({"user": {"username": "john_doe"}, "is_published": False})
|
227
|
+
```
|
228
|
+
|
229
|
+
## Convenient Query Methods
|
230
|
+
|
231
|
+
async-easy-model provides simplified methods for common query patterns:
|
232
|
+
|
233
|
+
```python
|
234
|
+
# Get all records with relationships loaded (default)
|
235
|
+
users = await User.all()
|
236
|
+
|
237
|
+
# Get all records ordered by a field (ascending)
|
238
|
+
users = await User.all(order_by="username")
|
239
|
+
|
240
|
+
# Get all records ordered by a field (descending)
|
241
|
+
newest_users = await User.all(order_by="-created_at")
|
242
|
+
|
243
|
+
# Get all records ordered by multiple fields
|
244
|
+
sorted_users = await User.all(order_by=["last_name", "first_name"])
|
245
|
+
|
246
|
+
# Get all records ordered by relationship fields
|
247
|
+
books = await Book.all(order_by="author.name")
|
248
|
+
|
249
|
+
# Get the first record
|
250
|
+
user = await User.first()
|
251
|
+
|
252
|
+
# Get the most recently created user
|
253
|
+
newest_user = await User.first(order_by="-created_at")
|
254
|
+
|
255
|
+
# Get a limited number of records
|
256
|
+
recent_users = await User.limit(10)
|
257
|
+
|
258
|
+
# Get a limited number of records with ordering
|
259
|
+
top_products = await Product.limit(5, order_by="-sales")
|
260
|
+
```
|
261
|
+
|
262
|
+
## Enhanced Relationship Handling
|
263
|
+
|
264
|
+
Using the models defined earlier, here's how to work with relationships:
|
265
|
+
|
266
|
+
```python
|
267
|
+
# Load all relationships automatically
|
268
|
+
post = await Post.select({"id": 1}, include_relationships=True)
|
269
|
+
print(post.user.username) # Access related objects directly
|
270
|
+
|
271
|
+
# Load specific relationships
|
272
|
+
post = await Post.get_with_related(1, ["user", "comments"])
|
273
|
+
|
274
|
+
# Load relationships after fetching
|
275
|
+
post = await Post.select({"id": 1})
|
276
|
+
await post.load_related(["user", "comments"])
|
277
|
+
|
278
|
+
# Insert with related objects in a single transaction
|
279
|
+
new_post = await Post.insert_with_related({
|
280
|
+
"title": "My Post",
|
281
|
+
"content": "Content here",
|
282
|
+
"user": {"username": "john_doe"}
|
283
|
+
})
|
284
|
+
|
285
|
+
# Convert to dictionary with nested relationships
|
286
|
+
post_dict = post.to_dict(include_relationships=True, max_depth=2)
|
287
|
+
```
|
288
|
+
|
289
|
+
## Automatic Relationship Detection
|
290
|
+
|
291
|
+
The package can automatically detect and set up bidirectional relationships between models:
|
292
|
+
|
293
|
+
```python
|
294
|
+
class User(EasyModel, table=True):
|
295
|
+
username: str
|
296
|
+
|
297
|
+
class Post(EasyModel, table=True):
|
298
|
+
title: str
|
299
|
+
user_id: int = Field(foreign_key="user.id")
|
300
|
+
|
301
|
+
# After init_db():
|
302
|
+
# - post.user relationship is automatically available
|
303
|
+
# - user.posts relationship is automatically available
|
304
|
+
```
|
305
|
+
|
306
|
+
## Database Configuration
|
307
|
+
|
308
|
+
```python
|
309
|
+
# SQLite Configuration
|
310
|
+
db_config.configure_sqlite("database.db")
|
311
|
+
db_config.configure_sqlite(":memory:") # In-memory database
|
312
|
+
|
313
|
+
# PostgreSQL Configuration
|
314
|
+
db_config.configure_postgres(
|
315
|
+
user="your_user",
|
316
|
+
password="your_password",
|
317
|
+
host="localhost",
|
318
|
+
port="5432",
|
319
|
+
database="your_database"
|
320
|
+
)
|
321
|
+
|
322
|
+
# Custom Connection URL
|
323
|
+
db_config.set_connection_url("postgresql+asyncpg://user:password@localhost:5432/database")
|
324
|
+
```
|
325
|
+
|
326
|
+
## Documentation
|
327
|
+
|
328
|
+
For more detailed documentation, please visit the [GitHub repository](https://github.com/puntorigen/easy_model) or refer to the [DOCS.md](https://github.com/puntorigen/easy_model/blob/main/DOCS.md) file.
|
329
|
+
|
330
|
+
## License
|
331
|
+
|
332
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
async_easy_model/__init__.py,sha256=A0U-LtrK9tjK-b94oDNJyc3XFmmnjQAHXP7Bs-FHx18,1642
|
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=TUTTLP47YWCdlsrsyf-7HmMAhPdlvTNZG416pA7UfCo,52958
|
5
|
+
async_easy_model/relationships.py,sha256=vR5BsJpGaDcecCcNlg9-ouZfxFXFQv5kOyiXhKp_T7A,3286
|
6
|
+
async_easy_model-0.2.2.dist-info/LICENSE,sha256=uwDkl6oHbRltW7vYKNc4doJyhtwhyrSNFFlPpKATwLE,1072
|
7
|
+
async_easy_model-0.2.2.dist-info/METADATA,sha256=QcX4beoXLL0fbPJ8ZVMAL4Q5QlXGgJfb4jjSjPx3Yqw,9474
|
8
|
+
async_easy_model-0.2.2.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
9
|
+
async_easy_model-0.2.2.dist-info/top_level.txt,sha256=e5_47sGmJnyxz2msfwU6C316EqmrSd9RGIYwZyWx68E,17
|
10
|
+
async_easy_model-0.2.2.dist-info/RECORD,,
|