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.
@@ -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,,