async-easy-model 0.2.1__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.
@@ -6,7 +6,7 @@ from typing import Optional, Any
6
6
  from .model import EasyModel, init_db, db_config
7
7
  from sqlmodel import Field, Relationship as SQLModelRelationship
8
8
 
9
- __version__ = "0.1.12"
9
+ __version__ = "0.2.2"
10
10
  __all__ = ["EasyModel", "init_db", "db_config", "Field", "Relationship", "Relation", "enable_auto_relationships", "disable_auto_relationships", "process_auto_relationships", "MigrationManager", "check_and_migrate_models"]
11
11
 
12
12
  # Create a more user-friendly Relationship function
async_easy_model/model.py CHANGED
@@ -511,6 +511,60 @@ class EasyModel(SQLModel):
511
511
  await session.rollback()
512
512
  raise
513
513
 
514
+ @classmethod
515
+ async def insert_with_related(
516
+ cls: Type[T],
517
+ data: Dict[str, Any],
518
+ related_data: Dict[str, List[Dict[str, Any]]] = None
519
+ ) -> T:
520
+ """
521
+ Create a model instance with related objects in a single transaction.
522
+
523
+ Args:
524
+ data: Dictionary of field values for the main model
525
+ related_data: Dictionary mapping relationship names to lists of data dictionaries
526
+ for creating related objects
527
+
528
+ Returns:
529
+ The created model instance with relationships loaded
530
+ """
531
+ if related_data is None:
532
+ related_data = {}
533
+
534
+ async with cls.get_session() as session:
535
+ # Create the main object
536
+ obj = cls(**data)
537
+ session.add(obj)
538
+ await session.flush() # Flush to get the ID
539
+
540
+ # Create related objects
541
+ for rel_name, items_data in related_data.items():
542
+ if not hasattr(cls, rel_name):
543
+ continue
544
+
545
+ rel_attr = getattr(cls, rel_name)
546
+ if not hasattr(rel_attr, "property"):
547
+ continue
548
+
549
+ # Get the related model class and the back reference attribute
550
+ related_model = rel_attr.property.mapper.class_
551
+ back_populates = getattr(rel_attr.property, "back_populates", None)
552
+
553
+ # Create each related object
554
+ for item_data in items_data:
555
+ # Set the back reference if it exists
556
+ if back_populates:
557
+ item_data[back_populates] = obj
558
+
559
+ related_obj = related_model(**item_data)
560
+ session.add(related_obj)
561
+
562
+ await session.commit()
563
+
564
+ # Refresh with relationships
565
+ await session.refresh(obj, attribute_names=list(related_data.keys()))
566
+ return obj
567
+
514
568
  @classmethod
515
569
  def _get_unique_fields(cls) -> List[str]:
516
570
  """
@@ -861,60 +915,6 @@ class EasyModel(SQLModel):
861
915
  await session.rollback()
862
916
  raise
863
917
 
864
- @classmethod
865
- async def create_with_related(
866
- cls: Type[T],
867
- data: Dict[str, Any],
868
- related_data: Dict[str, List[Dict[str, Any]]] = None
869
- ) -> T:
870
- """
871
- Create a model instance with related objects in a single transaction.
872
-
873
- Args:
874
- data: Dictionary of field values for the main model
875
- related_data: Dictionary mapping relationship names to lists of data dictionaries
876
- for creating related objects
877
-
878
- Returns:
879
- The created model instance with relationships loaded
880
- """
881
- if related_data is None:
882
- related_data = {}
883
-
884
- async with cls.get_session() as session:
885
- # Create the main object
886
- obj = cls(**data)
887
- session.add(obj)
888
- await session.flush() # Flush to get the ID
889
-
890
- # Create related objects
891
- for rel_name, items_data in related_data.items():
892
- if not hasattr(cls, rel_name):
893
- continue
894
-
895
- rel_attr = getattr(cls, rel_name)
896
- if not hasattr(rel_attr, "property"):
897
- continue
898
-
899
- # Get the related model class and the back reference attribute
900
- related_model = rel_attr.property.mapper.class_
901
- back_populates = getattr(rel_attr.property, "back_populates", None)
902
-
903
- # Create each related object
904
- for item_data in items_data:
905
- # Set the back reference if it exists
906
- if back_populates:
907
- item_data[back_populates] = obj
908
-
909
- related_obj = related_model(**item_data)
910
- session.add(related_obj)
911
-
912
- await session.commit()
913
-
914
- # Refresh with relationships
915
- await session.refresh(obj, attribute_names=list(related_data.keys()))
916
- return obj
917
-
918
918
  def to_dict(self, include_relationships: bool = True, max_depth: int = 4) -> Dict[str, Any]:
919
919
  """
920
920
  Convert the model instance to a dictionary.
@@ -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,,
@@ -1,343 +0,0 @@
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).
@@ -1,10 +0,0 @@
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,,