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.
@@ -1,533 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: async-easy-model
3
- Version: 0.1.12
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
46
- - Built on top of SQLModel and SQLAlchemy
47
- - Support for both PostgreSQL and SQLite databases
48
- - Common CRUD operations out of the box
49
- - Session management with context managers
50
- - Type hints for better IDE support
51
- - Automatic `created_at` and `updated_at` field management
52
- - **Enhanced relationship handling with eager loading and nested operations**
53
- - **Convenient query methods for retrieving records (all, first, limit)**
54
- - **Flexible ordering of query results with support for relationship fields**
55
- - **Simplified Field and Relationship definition syntax**
56
- - **Automatic relationship detection**
57
- - **Automatic schema migrations for evolving database models**
58
-
59
- ## Installation
60
-
61
- ```bash
62
- pip install async-easy-model
63
- ```
64
-
65
- ## Quick Start
66
-
67
- This section demonstrates the basic usage of EasyModel including database configuration, model definition, and fundamental CRUD operations.
68
-
69
- ```python
70
- from async_easy_model import EasyModel, init_db, db_config, Field
71
- from typing import Optional
72
- from datetime import datetime
73
-
74
- # Configure your database (choose one)
75
- # For SQLite:
76
- db_config.configure_sqlite("database.db")
77
- # For PostgreSQL:
78
- db_config.configure_postgres(
79
- user="your_user",
80
- password="your_password",
81
- host="localhost",
82
- port="5432",
83
- database="your_database"
84
- )
85
-
86
- # Define your model
87
- class User(EasyModel, table=True):
88
- id: Optional[int] = Field(default=None, primary_key=True)
89
- username: str = Field(unique=True)
90
- email: str
91
- # Note: created_at and updated_at fields are automatically included
92
- # and managed by EasyModel, so you don't need to define them.
93
-
94
- # Initialize your database (creates all tables)
95
- async def setup():
96
- await init_db()
97
-
98
- # Use it in your async code
99
- async def main():
100
- # Create a new user
101
- user = await User.insert({
102
- "username": "john_doe",
103
- "email": "john@example.com"
104
- })
105
-
106
- # Update user - updated_at will be automatically set
107
- updated_user = await User.update(1, {
108
- "email": "new_email@example.com"
109
- })
110
- print(f"Last update: {updated_user.updated_at}")
111
-
112
- # Delete user
113
- success = await User.delete(1)
114
- ```
115
-
116
- ## Working with Relationships
117
-
118
- EasyModel provides enhanced support for handling relationships between models. With the new `Relation` helper class, defining and working with relationships becomes more intuitive and type-safe.
119
-
120
- ### Defining Models with Relationships
121
-
122
- You can define relationships between models using either the new `Relation` helper class or the traditional SQLModel `Relationship` approach.
123
-
124
- ```python
125
- from typing import List, Optional
126
- from async_easy_model import EasyModel, Field, Relation
127
-
128
- class Author(EasyModel, table=True):
129
- name: str
130
- # Using Relation.many for a clear one-to-many relationship
131
- books: List["Book"] = Relation.many("author")
132
-
133
- class Book(EasyModel, table=True):
134
- title: str
135
- author_id: Optional[int] = Field(default=None, foreign_key="author.id")
136
- # Using Relation.one for a clear many-to-one relationship
137
- author: Optional["Author"] = Relation.one("books")
138
- ```
139
-
140
- The above example uses the new `Relation` class, which provides a more readable and intuitive way to define relationships. The `Relation` class offers:
141
-
142
- - `Relation.one()` - For defining a many-to-one relationship
143
- - `Relation.many()` - For defining a one-to-many relationship
144
-
145
- You can also use the traditional SQLModel `Relationship` approach, which is now exposed directly in the async_easy_model package:
146
-
147
- ```python
148
- from async_easy_model import EasyModel, Field, Relationship
149
- from typing import List, Optional
150
-
151
- class Author(EasyModel, table=True):
152
- name: str
153
- books: List["Book"] = Relationship(back_populates="author")
154
-
155
- class Book(EasyModel, table=True):
156
- title: str
157
- author_id: Optional[int] = Field(default=None, foreign_key="author.id")
158
- author: Optional["Author"] = Relationship(back_populates="books")
159
- ```
160
-
161
- ### Loading Related Objects
162
-
163
- EasyModel offers multiple ways to load related objects, allowing you to choose the most suitable approach for your specific use case.
164
-
165
- ```python
166
- # Fetch with all relationships eagerly loaded
167
- author = await Author.get_by_id(1, include_relationships=True)
168
- print(f"Author: {author.name}")
169
- print(f"Books: {[book.title for book in author.books]}")
170
-
171
- # Fetch specific relationships
172
- book = await Book.get_with_related(1, "author")
173
- print(f"Book: {book.title}")
174
- print(f"Author: {book.author.name}")
175
-
176
- # Load relationships after fetching
177
- another_book = await Book.get_by_id(2)
178
- await another_book.load_related("author")
179
- print(f"Author: {another_book.author.name}")
180
- ```
181
-
182
- ### Creating Objects with Relationships
183
-
184
- When creating objects with relationships, EasyModel allows you to create related objects in a single transaction using the `create_with_related` method.
185
-
186
- ```python
187
- # Create related objects in a single transaction
188
- new_author = await Author.create_with_related(
189
- data={"name": "Jane Doe"},
190
- related_data={
191
- "books": [
192
- {"title": "Book One"},
193
- {"title": "Book Two"}
194
- ]
195
- }
196
- )
197
-
198
- # Access the created relationships
199
- for book in new_author.books:
200
- print(f"Created book: {book.title}")
201
- ```
202
-
203
- ### Converting to Dictionary with Relationships
204
-
205
- The `to_dict()` method allows you to convert a model instance to a dictionary, including its relationships. This is particularly useful when you need to serialize your models for an API response.
206
-
207
- ```python
208
- # First ensure you have loaded the relationships
209
- author = await Author.get_with_related(1, "books")
210
-
211
- # Convert to dictionary including relationships
212
- author_dict = author.to_dict(include_relationships=True)
213
- print(f"Author: {author_dict['name']}")
214
- if 'books' in author_dict and author_dict['books']:
215
- print(f"Books: {[book['title'] for book in author_dict['books']]}")
216
-
217
- # Control the depth of nested relationships (default is 1)
218
- deep_dict = author.to_dict(include_relationships=True, max_depth=2)
219
- ```
220
-
221
- > **Note:** Always ensure that relationships are properly loaded before calling `to_dict()` with `include_relationships=True`. Use either `get_with_related()` or `get_by_id()` with `include_relationships=True` to ensure all relationship data is available.
222
-
223
- ## Automatic Relationship Detection
224
-
225
- Async EasyModel now supports automatic relationship detection based on foreign key fields. This makes it easier to work with related models without having to explicitly define relationships.
226
-
227
- ### How to enable automatic relationship detection
228
-
229
- ```python
230
- from async_easy_model import enable_auto_relationships, EasyModel, init_db, Field
231
- from typing import Optional
232
-
233
- # Enable automatic relationship detection before defining your models
234
- enable_auto_relationships()
235
-
236
- # Define your models with foreign key fields but without explicit relationships
237
- class Author(EasyModel, table=True):
238
- id: Optional[int] = Field(default=None, primary_key=True)
239
- name: str
240
- # No relationship definition needed for books!
241
-
242
- class Book(EasyModel, table=True):
243
- id: Optional[int] = Field(default=None, primary_key=True)
244
- title: str
245
- author_id: Optional[int] = Field(default=None, foreign_key="author.id")
246
- # No relationship definition needed for author!
247
-
248
- # Initialize database
249
- await init_db()
250
-
251
- # Now you can use relationships just like they were explicitly defined
252
- author = await Author.get_by_id(1, include_relationships=True)
253
- print(f"Author: {author.name}")
254
- print(f"Books: {[book.title for book in author.books]}")
255
-
256
- book = await Book.get_by_id(1, include_relationships=True)
257
- print(f"Book: {book.title}")
258
- print(f"Author: {book.author.name}")
259
- ```
260
-
261
- ### Compatibility with SQLModel
262
-
263
- If you encounter issues with automatic relationship detection due to conflicts with SQLModel's metaclass, you can:
264
-
265
- 1. Use the explicit relationship definitions with SQLModel's `Relationship`
266
- 2. Call `enable_auto_relationships(patch_metaclass=False)` and then set up relationships after model definition
267
-
268
- ```python
269
- from async_easy_model import enable_auto_relationships, EasyModel, Field, Relationship
270
- from typing import List, Optional
271
-
272
- # Enable without patching SQLModel's metaclass
273
- enable_auto_relationships(patch_metaclass=False)
274
-
275
- # Define models with explicit relationships
276
- class Author(EasyModel, table=True):
277
- id: Optional[int] = Field(default=None, primary_key=True)
278
- name: str
279
-
280
- # Explicitly define relationship
281
- books: List["Book"] = Relationship(back_populates="author")
282
-
283
- class Book(EasyModel, table=True):
284
- id: Optional[int] = Field(default=None, primary_key=True)
285
- title: str
286
- author_id: Optional[int] = Field(default=None, foreign_key="author.id")
287
-
288
- # Explicitly define relationship
289
- author: Optional[Author] = Relationship(back_populates="books")
290
- ```
291
-
292
- ### How Automatic Relationship Detection Works
293
-
294
- The automatic relationship detection feature works by:
295
-
296
- 1. Scanning model definitions for foreign key fields
297
- 2. Identifying the target model from the foreign key reference
298
- 3. Setting up bidirectional relationships between models
299
- 4. Registering relationships with SQLModel's metadata
300
-
301
- This allows you to simply define the foreign key fields and let the library handle the relationship setup. The naming convention used for automatic relationships is:
302
-
303
- - For to-one relationships: The name is derived from the foreign key field by removing the "_id" suffix (e.g., "author_id" → "author")
304
- - For to-many relationships: The pluralized name of the source model (e.g., "book" → "books")
305
-
306
- This follows the common convention in ORMs and makes the code more intuitive and self-documenting.
307
-
308
- ## Querying Records
309
-
310
- EasyModel provides powerful and flexible query methods that make it easy to retrieve and filter records from your database. The following sections demonstrate the various query methods available.
311
-
312
- ### Retrieving All Records
313
-
314
- The `all()` method allows you to retrieve all records of a model, with options for including relationships and ordering.
315
-
316
- ```python
317
- # Get all users
318
- all_users = await User.all()
319
- print(f"Total users: {len(all_users)}")
320
-
321
- # Get all users with their relationships
322
- all_users_with_relations = await User.all(include_relationships=True)
323
-
324
- # Get all users ordered by username
325
- ordered_users = await User.all(order_by="username")
326
-
327
- # Get all users ordered by creation date (newest first)
328
- newest_users = await User.all(order_by="-created_at")
329
-
330
- # Order by multiple fields
331
- complex_order = await User.all(order_by=["last_name", "first_name"])
332
- ```
333
-
334
- ### Getting the First Record
335
-
336
- The `first()` method allows you to retrieve the first record that matches your criteria, with options for ordering and including relationships.
337
-
338
- ```python
339
- # Get the first user
340
- first_user = await User.first()
341
- if first_user:
342
- print(f"First user: {first_user.username}")
343
-
344
- # Get the first user with relationships
345
- first_user_with_relations = await User.first(include_relationships=True)
346
-
347
- # Get the oldest user (ordered by created_at)
348
- oldest_user = await User.first(order_by="created_at")
349
- ```
350
-
351
- ### Limiting Results
352
-
353
- The `limit()` method allows you to retrieve a limited number of records, with options for ordering and including relationships.
354
-
355
- ```python
356
- # Get the first 10 users
357
- recent_users = await User.limit(10)
358
- print(f"Recent users: {[user.username for user in recent_users]}")
359
-
360
- # Get the first 5 users with relationships
361
- recent_users_with_relations = await User.limit(5, include_relationships=True)
362
-
363
- # Get the 5 most recently created users
364
- newest_users = await User.limit(5, order_by="-created_at")
365
- ```
366
-
367
- ### Filtering with Ordering
368
-
369
- The `get_by_attribute()` method allows you to filter records by attribute values, with options for ordering and including relationships.
370
-
371
- ```python
372
- # Get all active users ordered by username
373
- active_users = await User.get_by_attribute(
374
- all=True,
375
- is_active=True,
376
- order_by="username"
377
- )
378
-
379
- # Get the most recent user in a specific category
380
- latest_admin = await User.get_by_attribute(
381
- role="admin",
382
- order_by="-created_at"
383
- )
384
- ```
385
-
386
- ### Ordering by Relationship Fields
387
-
388
- EasyModel supports ordering by relationship fields, allowing you to sort records based on attributes of related models.
389
-
390
- ```python
391
- # Get all books ordered by author name
392
- books_by_author = await Book.all(order_by="author.name")
393
-
394
- # Get users ordered by their latest post date
395
- users_by_post = await User.all(order_by="-posts.created_at")
396
- ```
397
-
398
- ## Automatic Schema Migrations
399
-
400
- EasyModel now includes automatic database migration capabilities, similar to Alembic but requiring no manual configuration. This feature allows your database schema to automatically evolve as your model definitions change.
401
-
402
- ### How Migrations Work
403
-
404
- When your application starts, EasyModel:
405
-
406
- 1. Tracks your model schemas by generating and storing hash codes
407
- 2. Detects when model definitions have changed since the last run
408
- 3. Automatically applies appropriate migrations to update your database schema
409
-
410
- This process ensures that your database tables always match your model definitions, without requiring you to write manual migration scripts.
411
-
412
- ```python
413
- from async_easy_model import EasyModel, init_db, db_config
414
-
415
- # Configure your database
416
- db_config.configure_sqlite("database.db")
417
-
418
- # Define your model
419
- class User(EasyModel, table=True):
420
- username: str
421
- email: str
422
- # Later, you might add a new field:
423
- # is_active: bool = Field(default=True)
424
-
425
- # Initialize database - migrations happen automatically
426
- async def setup():
427
- await init_db()
428
- # Any model changes will be detected and migrated automatically
429
- ```
430
-
431
- ### Migration Storage
432
-
433
- Migrations are tracked in a `.easy_model_migrations` directory, which contains:
434
-
435
- - `model_hashes.json`: Stores hashes of your model definitions
436
- - `migration_history.json`: Records all migrations that have been applied
437
-
438
- ### Advanced Migration Control
439
-
440
- For more control over the migration process, you can use the `MigrationManager` directly:
441
-
442
- ```python
443
- from async_easy_model import MigrationManager, EasyModel
444
- from your_app.models import User, Post
445
-
446
- async def check_pending_migrations():
447
- migration_manager = MigrationManager()
448
- changes = await migration_manager.detect_model_changes([User, Post])
449
-
450
- if changes:
451
- print("Pending model changes:")
452
- for model_name, info in changes.items():
453
- print(f"- {model_name}: {info['status']}")
454
- else:
455
- print("All models are up to date.")
456
-
457
- async def apply_migrations():
458
- migration_manager = MigrationManager()
459
- results = await migration_manager.migrate_models([User, Post])
460
-
461
- if results:
462
- print("Applied migrations:")
463
- for model_name, operations in results.items():
464
- print(f"- {model_name}: {len(operations)} operations")
465
- ```
466
-
467
- ## Configuration
468
-
469
- EasyModel supports multiple ways to configure your database connection, making it easy to adapt to different environments and requirements.
470
-
471
- ### 1. Using Environment Variables
472
-
473
- Environment variables provide a secure and flexible way to configure your database connection, especially for production deployments.
474
-
475
- For PostgreSQL:
476
- ```bash
477
- POSTGRES_USER=your_user
478
- POSTGRES_PASSWORD=your_password
479
- POSTGRES_HOST=localhost
480
- POSTGRES_PORT=5432
481
- POSTGRES_DB=your_database
482
- ```
483
-
484
- For SQLite:
485
- ```bash
486
- SQLITE_FILE=database.db
487
- ```
488
-
489
- ### 2. Using Configuration Methods
490
-
491
- Configuration methods provide a programmatic way to set up your database connection, which is often more convenient during development.
492
-
493
- For PostgreSQL:
494
- ```python
495
- from async_easy_model import db_config
496
-
497
- db_config.configure_postgres(
498
- user="your_user",
499
- password="your_password",
500
- host="localhost",
501
- port="5432",
502
- database="your_database"
503
- )
504
- ```
505
-
506
- For SQLite:
507
- ```python
508
- from async_easy_model import db_config
509
-
510
- db_config.configure_sqlite("database.db")
511
- ```
512
-
513
- ## Examples
514
-
515
- For more detailed examples and practical applications, check out the `examples` directory in the repository:
516
-
517
- - `examples/relationship_example.py`: Demonstrates the enhanced relationship handling features
518
- - `examples/diario_example.py`: Shows how to use relationship features with diary entries
519
- - `examples/query_methods_example.py`: Shows how to use the query methods with ordering
520
- - `examples/minimal_working_example.py`: Basic example of model definition and CRUD operations
521
- - `examples/simple_auto_detection.py`: Demonstrates automatic relationship detection with SQLite
522
- - `examples/simple_auto_relationship.py`: Shows how to use auto-relationships with explicit definitions
523
- - `examples/comprehensive_auto_rel_example.py`: Comprehensive example with multiple models and relationships
524
-
525
- For complete documentation of all features, see the [DOCS.md](DOCS.md) file.
526
-
527
- ## Contributing
528
-
529
- Contributions are welcome! Please feel free to submit a Pull Request.
530
-
531
- ## License
532
-
533
- This project is licensed under the MIT License - see the LICENSE file for details.
@@ -1,10 +0,0 @@
1
- async_easy_model/__init__.py,sha256=JcNQU_W8DUFnGVoOkbwfLDz5akT6jjMNVYLzWy_HA6w,1643
2
- async_easy_model/auto_relationships.py,sha256=lXdS8Um81gJhM6Ur-ln04KhW9EDGZFm9MY2BCe8UvR8,21882
3
- async_easy_model/migrations.py,sha256=rYDGCGlruSugAmPfdIF2-uhyG6UvC_2qbF3BXJ084qI,17803
4
- async_easy_model/model.py,sha256=qPpFQkSPb37n72s40LiKXWrAJUu3gHd3BCtP49IzT3Q,25379
5
- async_easy_model/relationships.py,sha256=vR5BsJpGaDcecCcNlg9-ouZfxFXFQv5kOyiXhKp_T7A,3286
6
- async_easy_model-0.1.12.dist-info/LICENSE,sha256=uwDkl6oHbRltW7vYKNc4doJyhtwhyrSNFFlPpKATwLE,1072
7
- async_easy_model-0.1.12.dist-info/METADATA,sha256=aJgIhQQWhtKTf_ZMIfSC-27gVVRcG2pJ69tETCHVhx8,18238
8
- async_easy_model-0.1.12.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
9
- async_easy_model-0.1.12.dist-info/top_level.txt,sha256=e5_47sGmJnyxz2msfwU6C316EqmrSd9RGIYwZyWx68E,17
10
- async_easy_model-0.1.12.dist-info/RECORD,,