async-easy-model


Nameasync-easy-model JSON
Version 0.4.2 PyPI version JSON
download
home_pagehttps://github.com/puntorigen/easy-model
SummaryA simplified SQLModel-based ORM for async database operations
upload_time2025-07-26 01:47:04
maintainerNone
docs_urlNone
authorPablo Schaffner
requires_python>=3.7
licenseNone
keywords orm sqlmodel database async postgresql sqlite mysql
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # async-easy-model

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.

<p align="center">
  <img src="https://img.shields.io/pypi/v/async-easy-model" alt="PyPI Version">
  <img src="https://img.shields.io/pypi/pyversions/async-easy-model" alt="Python Versions">
  <img src="https://img.shields.io/github/license/puntorigen/easy_model" alt="License">
</p>

## Features

- 🚀 Easy-to-use async database operations with standardized methods
- 🔄 Intuitive APIs with sensible defaults for rapid development
- 📊 Dictionary-based CRUD operations (select, insert, update, delete)
- 🔗 Enhanced relationship handling with eager loading and nested operations
- 🔍 Powerful query methods with flexible ordering support
- ⚙️ Automatic relationship detection and bidirectional setup
- 📱 Support for PostgreSQL, SQLite, and MySQL databases
- 🛠️ Built on top of SQLModel and SQLAlchemy for robust performance
- 📝 Type hints for better IDE support
- 🕒 Automatic `id`, `created_at` and `updated_at` fields provided by default
- ⏰ **PostgreSQL DateTime Compatibility**: Automatic timezone-aware to timezone-naive datetime conversion for PostgreSQL TIMESTAMP WITHOUT TIME ZONE columns
- 🔄 Automatic schema migrations for evolving database models
- 📊 Visualization of database schema using Mermaid ER diagrams
- 📋 **JSON Column Support**: Native support for JSON columns with proper serialization in migrations

## Installation

```bash
pip install async-easy-model
```

## Basic Usage

```python
from async_easy_model import EasyModel, init_db, db_config, Field
from typing import Optional
from datetime import datetime

# Configure your database
db_config.configure_sqlite("database.db")

# Define your model
class User(EasyModel, table=True):
    #no need to specify id, created_at or updated_at since EasyModel provides them by default
    username: str = Field(unique=True)
    email: str

# Initialize your database
async def setup():
    await init_db()

# Use it in your async code
async def main():
    await setup()
    # Create a new user
    user = await User.insert({
        "username": "john_doe",
        "email": "john@example.com"
    })
    
    # Get user ID
    print(f"New user id: {user.id}")
```

## CRUD Operations

First, let's define some models that we'll use throughout the examples:

```python
from async_easy_model import EasyModel, Field
from typing import Optional, List
from datetime import datetime

class User(EasyModel, table=True):
    username: str = Field(unique=True)
    email: str
    is_active: bool = Field(default=True)
    
class Post(EasyModel, table=True):
    title: str
    content: str
    user_id: Optional[int] = Field(default=None, foreign_key="user.id")
    
class Comment(EasyModel, table=True):
    text: str
    post_id: Optional[int] = Field(default=None, foreign_key="post.id")
    user_id: Optional[int] = Field(default=None, foreign_key="user.id")
    
class Department(EasyModel, table=True):
    name: str = Field(unique=True)
    
class Product(EasyModel, table=True):
    name: str
    price: float
    sales: int = Field(default=0)
    
class Book(EasyModel, table=True):
    title: str
    author_id: Optional[int] = Field(default=None, foreign_key="author.id")
    
class Author(EasyModel, table=True):
    name: str
```

### Create (Insert)

```python
# Insert a single record
user = await User.insert({
    "username": "john_doe",
    "email": "john@example.com"
})

# Insert multiple records
users = await User.insert([
    {"username": "user1", "email": "user1@example.com"},
    {"username": "user2", "email": "user2@example.com"}
])

# Insert with nested relationships
new_post = await Post.insert({
    "title": "My Post",
    "content": "Content here",
    "user": {"username": "jane_doe"},  # Will automatically link to existing user
    "comments": [  # Create multiple comments in a single transaction
        {"text": "Great post!", "user": {"username": "reader1"}},
        {"text": "Thanks for sharing", "user": {"username": "reader2"}}
    ]
})
# Access nested data without requerying
print(f"Post by {new_post.user.username} with {len(new_post.comments)} comments")

# Insert with nested one-to-many relationships 
publisher = await Publisher.insert({
    "name": "Example Publisher",
    "books": [  # List of nested objects
        {
            "title": "Python Mastery",
            "genres": [
                {"name": "Programming"},
                {"name": "Education"}
            ]
        },
        {"title": "Data Science Handbook"}
    ]
})
# Access nested relationships immediately
print(f"Publisher: {publisher.name} with {len(publisher.books)} books")
print(f"First book genres: {[g.name for g in publisher.books[0].genres]}")
```

### Read (Retrieve)

```python
# Select by ID
user = await User.select({"id": 1})

# Select with criteria
users = await User.select({"is_active": True}, all=True)

# Select first matching record
first_user = await User.select({"is_active": True}, first=True)

# Select all records
all_users = await User.select({}, all=True)

# Select with wildcard pattern matching
gmail_users = await User.select({"email": "*@gmail.com"}, all=True)

# Select with ordering
recent_users = await User.select({}, order_by="-created_at", all=True)

# Select with limit
latest_posts = await Post.select({}, order_by="-created_at", limit=5)
# Note: limit > 1 automatically sets all=True

# Select with multiple ordering fields
sorted_users = await User.select({}, order_by=["last_name", "first_name"], all=True)

# Select with relationship ordering
posts_by_author = await Post.select({}, order_by="user.username", all=True)
```

### Update

```python
# Update by ID
user = await User.update({"is_active": False}, 1)

# Update by criteria
count = await User.update(
    {"is_active": False},
    {"last_login": None}  # Set all users without login to inactive
)

# Update with relationships
await User.update(
    {"department": {"name": "Sales"}},  # Update department relationship
    {"username": "john_doe"}
)
```

### Delete

```python
# Delete by ID
success = await User.delete(1)

# Delete by criteria
deleted_count = await User.delete({"is_active": False})

# Delete with compound criteria
await Post.delete({"user": {"username": "john_doe"}, "is_published": False})
```

## Database Schema Visualization

The package includes a `ModelVisualizer` class that makes it easy to generate Entity-Relationship (ER) diagrams for your database models using Mermaid syntax.

```python
from async_easy_model import EasyModel, init_db, db_config, ModelVisualizer

# Initialize your models and database
await init_db()

# Create a visualizer
visualizer = ModelVisualizer()

# Generate a Mermaid ER diagram
er_diagram = visualizer.mermaid()
print(er_diagram)

# Generate a shareable link to view the diagram online
er_link = visualizer.mermaid_link()
print(er_link)

# Customize the diagram title
visualizer.set_title("My Project Database Schema")
custom_diagram = visualizer.mermaid()
```

### Example Mermaid ER Diagram Output

```mermaid
---
title: EasyModel Table Schemas
config:
    layout: elk
---
erDiagram
    author {
        number id PK
        string name "required"
        string email
    }
    book {
        number id PK
        string title "required"
        number author_id FK
        string isbn
        number published_year
        author author "virtual"
        tag[] tags "virtual"
    }
    tag {
        number id PK
        string name "required"
        book[] books "virtual"
    }
    book_tag {
        number id PK
        number book_id FK "required"
        number tag_id FK "required"
        book book "virtual"
        tag tag "virtual"
    }
    review {
        number id PK
        number book_id FK "required"
        number rating "required"
        string comment
        string reviewer_name "required"
        book book "virtual"
    }
    book ||--o{ author : "author_id"
    book_tag ||--o{ book : "book_id"
    book_tag ||--o{ tag : "tag_id"
    book }o--o{ tag : "many-to-many"
    review ||--o{ book : "book_id"
```

The diagram automatically:
- Shows all tables with their fields and data types
- Identifies primary keys (PK) and foreign keys (FK)
- Shows required fields and virtual relationships
- Visualizes relationships between tables with proper cardinality
- Properly handles many-to-many relationships

## Convenient Query Methods

async-easy-model provides simplified methods for common query patterns:

```python
# Get all records with relationships loaded (default)
users = await User.all()

# Get all records ordered by a field (ascending)
users = await User.all(order_by="username")

# Get all records ordered by a field (descending)
newest_users = await User.all(order_by="-created_at")

# Get all records ordered by multiple fields
sorted_users = await User.all(order_by=["last_name", "first_name"])

# Get all records ordered by relationship fields
books = await Book.all(order_by="author.name")

# Get the first record 
user = await User.first()

# Get the most recently created user
newest_user = await User.first(order_by="-created_at")

# Get a limited number of records
recent_users = await User.limit(10)

# Get a limited number of records with ordering
top_products = await Product.limit(5, order_by="-sales")
```

## Enhanced Relationship Handling

Using the models defined earlier, here's how to work with relationships:

```python
# Load all relationships automatically
post = await Post.select({"id": 1})
print(post.user.username)  # Access related objects directly

# Load specific relationships
post = await Post.get_with_related(1, ["user", "comments"])

# Load relationships after fetching
post = await Post.select({"id": 1}, include_relationships=False)
await post.load_related(["user", "comments"])

# Insert with nested relationships
new_post = await Post.insert({
    "title": "My Post",
    "content": "Content here",
    "user": {"username": "jane_doe"},  # Will automatically link to existing user
    "comments": [  # Create multiple comments in a single transaction
        {"text": "Great post!", "user": {"username": "reader1"}},
        {"text": "Thanks for sharing", "user": {"username": "reader2"}}
    ]
})
# Access nested data without requerying
print(f"Post by {new_post.user.username} with {len(new_post.comments)} comments")

# Convert to dictionary with nested relationships
post_dict = post.to_dict(include_relationships=True, max_depth=2)
```

## Automatic Relationship Detection

The package can automatically detect and set up bidirectional relationships between models:

```python
class User(EasyModel, table=True):
    username: str

class Post(EasyModel, table=True):
    title: str
    user_id: int = Field(foreign_key="user.id")

# After init_db():
# - post.user relationship is automatically available
# - user.posts relationship is automatically available
```

## Database Configuration

```python
# SQLite Configuration
db_config.configure_sqlite("database.db")
db_config.configure_sqlite(":memory:")  # In-memory database

# PostgreSQL Configuration
db_config.configure_postgres(
    user="your_user",
    password="your_password",
    host="localhost",
    port="5432",
    database="your_database"
)

# MySQL Configuration
db_config.configure_mysql(
    user="your_user",
    password="your_password",
    host="localhost",
    port="3306",
    database="your_database"
)

# Custom Connection URL
db_config.set_connection_url("postgresql+asyncpg://user:password@localhost:5432/database")
```

### Configurable Default for Relationship Loading

**New in v0.3.9**: You can now set a global default for `include_relationships` behavior across all query methods:

```python
# Configure with default_include_relationships=False for better performance
db_config.configure_sqlite("database.db", default_include_relationships=False)

# Or for PostgreSQL
db_config.configure_postgres(
    user="your_user",
    password="your_password",
    host="localhost",
    port="5432",
    database="your_database",
    default_include_relationships=False  # Set global default
)

# Or for MySQL
db_config.configure_mysql(
    user="your_user",
    password="your_password",
    host="localhost",
    port="3306",
    database="your_database",
    default_include_relationships=False  # Set global default
)
```

**Benefits:**
- **Performance**: Set `default_include_relationships=False` to avoid loading relationships by default, improving query performance
- **Flexibility**: Still override per method call with explicit `True` or `False` values
- **Backward Compatible**: Defaults to `True` if not specified, maintaining existing behavior

**Usage Examples:**
```python
# With default_include_relationships=False configured:
users = await User.all()  # No relationships loaded (faster)
users_with_rels = await User.all(include_relationships=True)  # Override to load relationships

# With default_include_relationships=True configured (default behavior):
users = await User.all()  # Relationships loaded
users_no_rels = await User.all(include_relationships=False)  # Override to skip relationships
```

### Database Initialization Options

**New in v0.4.1**: The `init_db()` function now supports configurable auto-relationships handling:

```python
# Default behavior (auto-detect auto-relationships availability)
await init_db()

# Force enable auto-relationships (will warn if not available)
await init_db(has_auto_relationships=True)

# Force disable auto-relationships
await init_db(has_auto_relationships=False)

# Combined with other parameters
await init_db(
    migrate=True,
    model_classes=[User, Post, Comment],
    has_auto_relationships=False
)
```

**Benefits:**
- **Control**: Explicitly enable or disable auto-relationships functionality
- **Reliability**: Auto-relationships errors now issue warnings instead of stopping database initialization
- **Flexibility**: Can be combined with migration control and specific model classes
- **Robust**: Database initialization continues even if auto-relationships fail

**Parameter Priority:**
1. Explicit `has_auto_relationships` parameter (if provided)
2. Auto-detection of auto-relationships availability (default)
3. Fallback to available functionality

## Documentation

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/master/DOCS.md) file.

## License

This project is licensed under the MIT License - see the LICENSE file for details.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/puntorigen/easy-model",
    "name": "async-easy-model",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "orm, sqlmodel, database, async, postgresql, sqlite, mysql",
    "author": "Pablo Schaffner",
    "author_email": "pablo@puntorigen.com",
    "download_url": "https://files.pythonhosted.org/packages/29/80/e47bbfc7dfe6110ca41c2b034933f7efc75c06f39183bae95bf85a463d57/async_easy_model-0.4.2.tar.gz",
    "platform": null,
    "description": "# async-easy-model\n\nA 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.\n\n<p align=\"center\">\n  <img src=\"https://img.shields.io/pypi/v/async-easy-model\" alt=\"PyPI Version\">\n  <img src=\"https://img.shields.io/pypi/pyversions/async-easy-model\" alt=\"Python Versions\">\n  <img src=\"https://img.shields.io/github/license/puntorigen/easy_model\" alt=\"License\">\n</p>\n\n## Features\n\n- \ud83d\ude80 Easy-to-use async database operations with standardized methods\n- \ud83d\udd04 Intuitive APIs with sensible defaults for rapid development\n- \ud83d\udcca Dictionary-based CRUD operations (select, insert, update, delete)\n- \ud83d\udd17 Enhanced relationship handling with eager loading and nested operations\n- \ud83d\udd0d Powerful query methods with flexible ordering support\n- \u2699\ufe0f Automatic relationship detection and bidirectional setup\n- \ud83d\udcf1 Support for PostgreSQL, SQLite, and MySQL databases\n- \ud83d\udee0\ufe0f Built on top of SQLModel and SQLAlchemy for robust performance\n- \ud83d\udcdd Type hints for better IDE support\n- \ud83d\udd52 Automatic `id`, `created_at` and `updated_at` fields provided by default\n- \u23f0 **PostgreSQL DateTime Compatibility**: Automatic timezone-aware to timezone-naive datetime conversion for PostgreSQL TIMESTAMP WITHOUT TIME ZONE columns\n- \ud83d\udd04 Automatic schema migrations for evolving database models\n- \ud83d\udcca Visualization of database schema using Mermaid ER diagrams\n- \ud83d\udccb **JSON Column Support**: Native support for JSON columns with proper serialization in migrations\n\n## Installation\n\n```bash\npip install async-easy-model\n```\n\n## Basic Usage\n\n```python\nfrom async_easy_model import EasyModel, init_db, db_config, Field\nfrom typing import Optional\nfrom datetime import datetime\n\n# Configure your database\ndb_config.configure_sqlite(\"database.db\")\n\n# Define your model\nclass User(EasyModel, table=True):\n    #no need to specify id, created_at or updated_at since EasyModel provides them by default\n    username: str = Field(unique=True)\n    email: str\n\n# Initialize your database\nasync def setup():\n    await init_db()\n\n# Use it in your async code\nasync def main():\n    await setup()\n    # Create a new user\n    user = await User.insert({\n        \"username\": \"john_doe\",\n        \"email\": \"john@example.com\"\n    })\n    \n    # Get user ID\n    print(f\"New user id: {user.id}\")\n```\n\n## CRUD Operations\n\nFirst, let's define some models that we'll use throughout the examples:\n\n```python\nfrom async_easy_model import EasyModel, Field\nfrom typing import Optional, List\nfrom datetime import datetime\n\nclass User(EasyModel, table=True):\n    username: str = Field(unique=True)\n    email: str\n    is_active: bool = Field(default=True)\n    \nclass Post(EasyModel, table=True):\n    title: str\n    content: str\n    user_id: Optional[int] = Field(default=None, foreign_key=\"user.id\")\n    \nclass Comment(EasyModel, table=True):\n    text: str\n    post_id: Optional[int] = Field(default=None, foreign_key=\"post.id\")\n    user_id: Optional[int] = Field(default=None, foreign_key=\"user.id\")\n    \nclass Department(EasyModel, table=True):\n    name: str = Field(unique=True)\n    \nclass Product(EasyModel, table=True):\n    name: str\n    price: float\n    sales: int = Field(default=0)\n    \nclass Book(EasyModel, table=True):\n    title: str\n    author_id: Optional[int] = Field(default=None, foreign_key=\"author.id\")\n    \nclass Author(EasyModel, table=True):\n    name: str\n```\n\n### Create (Insert)\n\n```python\n# Insert a single record\nuser = await User.insert({\n    \"username\": \"john_doe\",\n    \"email\": \"john@example.com\"\n})\n\n# Insert multiple records\nusers = await User.insert([\n    {\"username\": \"user1\", \"email\": \"user1@example.com\"},\n    {\"username\": \"user2\", \"email\": \"user2@example.com\"}\n])\n\n# Insert with nested relationships\nnew_post = await Post.insert({\n    \"title\": \"My Post\",\n    \"content\": \"Content here\",\n    \"user\": {\"username\": \"jane_doe\"},  # Will automatically link to existing user\n    \"comments\": [  # Create multiple comments in a single transaction\n        {\"text\": \"Great post!\", \"user\": {\"username\": \"reader1\"}},\n        {\"text\": \"Thanks for sharing\", \"user\": {\"username\": \"reader2\"}}\n    ]\n})\n# Access nested data without requerying\nprint(f\"Post by {new_post.user.username} with {len(new_post.comments)} comments\")\n\n# Insert with nested one-to-many relationships \npublisher = await Publisher.insert({\n    \"name\": \"Example Publisher\",\n    \"books\": [  # List of nested objects\n        {\n            \"title\": \"Python Mastery\",\n            \"genres\": [\n                {\"name\": \"Programming\"},\n                {\"name\": \"Education\"}\n            ]\n        },\n        {\"title\": \"Data Science Handbook\"}\n    ]\n})\n# Access nested relationships immediately\nprint(f\"Publisher: {publisher.name} with {len(publisher.books)} books\")\nprint(f\"First book genres: {[g.name for g in publisher.books[0].genres]}\")\n```\n\n### Read (Retrieve)\n\n```python\n# Select by ID\nuser = await User.select({\"id\": 1})\n\n# Select with criteria\nusers = await User.select({\"is_active\": True}, all=True)\n\n# Select first matching record\nfirst_user = await User.select({\"is_active\": True}, first=True)\n\n# Select all records\nall_users = await User.select({}, all=True)\n\n# Select with wildcard pattern matching\ngmail_users = await User.select({\"email\": \"*@gmail.com\"}, all=True)\n\n# Select with ordering\nrecent_users = await User.select({}, order_by=\"-created_at\", all=True)\n\n# Select with limit\nlatest_posts = await Post.select({}, order_by=\"-created_at\", limit=5)\n# Note: limit > 1 automatically sets all=True\n\n# Select with multiple ordering fields\nsorted_users = await User.select({}, order_by=[\"last_name\", \"first_name\"], all=True)\n\n# Select with relationship ordering\nposts_by_author = await Post.select({}, order_by=\"user.username\", all=True)\n```\n\n### Update\n\n```python\n# Update by ID\nuser = await User.update({\"is_active\": False}, 1)\n\n# Update by criteria\ncount = await User.update(\n    {\"is_active\": False},\n    {\"last_login\": None}  # Set all users without login to inactive\n)\n\n# Update with relationships\nawait User.update(\n    {\"department\": {\"name\": \"Sales\"}},  # Update department relationship\n    {\"username\": \"john_doe\"}\n)\n```\n\n### Delete\n\n```python\n# Delete by ID\nsuccess = await User.delete(1)\n\n# Delete by criteria\ndeleted_count = await User.delete({\"is_active\": False})\n\n# Delete with compound criteria\nawait Post.delete({\"user\": {\"username\": \"john_doe\"}, \"is_published\": False})\n```\n\n## Database Schema Visualization\n\nThe package includes a `ModelVisualizer` class that makes it easy to generate Entity-Relationship (ER) diagrams for your database models using Mermaid syntax.\n\n```python\nfrom async_easy_model import EasyModel, init_db, db_config, ModelVisualizer\n\n# Initialize your models and database\nawait init_db()\n\n# Create a visualizer\nvisualizer = ModelVisualizer()\n\n# Generate a Mermaid ER diagram\ner_diagram = visualizer.mermaid()\nprint(er_diagram)\n\n# Generate a shareable link to view the diagram online\ner_link = visualizer.mermaid_link()\nprint(er_link)\n\n# Customize the diagram title\nvisualizer.set_title(\"My Project Database Schema\")\ncustom_diagram = visualizer.mermaid()\n```\n\n### Example Mermaid ER Diagram Output\n\n```mermaid\n---\ntitle: EasyModel Table Schemas\nconfig:\n    layout: elk\n---\nerDiagram\n    author {\n        number id PK\n        string name \"required\"\n        string email\n    }\n    book {\n        number id PK\n        string title \"required\"\n        number author_id FK\n        string isbn\n        number published_year\n        author author \"virtual\"\n        tag[] tags \"virtual\"\n    }\n    tag {\n        number id PK\n        string name \"required\"\n        book[] books \"virtual\"\n    }\n    book_tag {\n        number id PK\n        number book_id FK \"required\"\n        number tag_id FK \"required\"\n        book book \"virtual\"\n        tag tag \"virtual\"\n    }\n    review {\n        number id PK\n        number book_id FK \"required\"\n        number rating \"required\"\n        string comment\n        string reviewer_name \"required\"\n        book book \"virtual\"\n    }\n    book ||--o{ author : \"author_id\"\n    book_tag ||--o{ book : \"book_id\"\n    book_tag ||--o{ tag : \"tag_id\"\n    book }o--o{ tag : \"many-to-many\"\n    review ||--o{ book : \"book_id\"\n```\n\nThe diagram automatically:\n- Shows all tables with their fields and data types\n- Identifies primary keys (PK) and foreign keys (FK)\n- Shows required fields and virtual relationships\n- Visualizes relationships between tables with proper cardinality\n- Properly handles many-to-many relationships\n\n## Convenient Query Methods\n\nasync-easy-model provides simplified methods for common query patterns:\n\n```python\n# Get all records with relationships loaded (default)\nusers = await User.all()\n\n# Get all records ordered by a field (ascending)\nusers = await User.all(order_by=\"username\")\n\n# Get all records ordered by a field (descending)\nnewest_users = await User.all(order_by=\"-created_at\")\n\n# Get all records ordered by multiple fields\nsorted_users = await User.all(order_by=[\"last_name\", \"first_name\"])\n\n# Get all records ordered by relationship fields\nbooks = await Book.all(order_by=\"author.name\")\n\n# Get the first record \nuser = await User.first()\n\n# Get the most recently created user\nnewest_user = await User.first(order_by=\"-created_at\")\n\n# Get a limited number of records\nrecent_users = await User.limit(10)\n\n# Get a limited number of records with ordering\ntop_products = await Product.limit(5, order_by=\"-sales\")\n```\n\n## Enhanced Relationship Handling\n\nUsing the models defined earlier, here's how to work with relationships:\n\n```python\n# Load all relationships automatically\npost = await Post.select({\"id\": 1})\nprint(post.user.username)  # Access related objects directly\n\n# Load specific relationships\npost = await Post.get_with_related(1, [\"user\", \"comments\"])\n\n# Load relationships after fetching\npost = await Post.select({\"id\": 1}, include_relationships=False)\nawait post.load_related([\"user\", \"comments\"])\n\n# Insert with nested relationships\nnew_post = await Post.insert({\n    \"title\": \"My Post\",\n    \"content\": \"Content here\",\n    \"user\": {\"username\": \"jane_doe\"},  # Will automatically link to existing user\n    \"comments\": [  # Create multiple comments in a single transaction\n        {\"text\": \"Great post!\", \"user\": {\"username\": \"reader1\"}},\n        {\"text\": \"Thanks for sharing\", \"user\": {\"username\": \"reader2\"}}\n    ]\n})\n# Access nested data without requerying\nprint(f\"Post by {new_post.user.username} with {len(new_post.comments)} comments\")\n\n# Convert to dictionary with nested relationships\npost_dict = post.to_dict(include_relationships=True, max_depth=2)\n```\n\n## Automatic Relationship Detection\n\nThe package can automatically detect and set up bidirectional relationships between models:\n\n```python\nclass User(EasyModel, table=True):\n    username: str\n\nclass Post(EasyModel, table=True):\n    title: str\n    user_id: int = Field(foreign_key=\"user.id\")\n\n# After init_db():\n# - post.user relationship is automatically available\n# - user.posts relationship is automatically available\n```\n\n## Database Configuration\n\n```python\n# SQLite Configuration\ndb_config.configure_sqlite(\"database.db\")\ndb_config.configure_sqlite(\":memory:\")  # In-memory database\n\n# PostgreSQL Configuration\ndb_config.configure_postgres(\n    user=\"your_user\",\n    password=\"your_password\",\n    host=\"localhost\",\n    port=\"5432\",\n    database=\"your_database\"\n)\n\n# MySQL Configuration\ndb_config.configure_mysql(\n    user=\"your_user\",\n    password=\"your_password\",\n    host=\"localhost\",\n    port=\"3306\",\n    database=\"your_database\"\n)\n\n# Custom Connection URL\ndb_config.set_connection_url(\"postgresql+asyncpg://user:password@localhost:5432/database\")\n```\n\n### Configurable Default for Relationship Loading\n\n**New in v0.3.9**: You can now set a global default for `include_relationships` behavior across all query methods:\n\n```python\n# Configure with default_include_relationships=False for better performance\ndb_config.configure_sqlite(\"database.db\", default_include_relationships=False)\n\n# Or for PostgreSQL\ndb_config.configure_postgres(\n    user=\"your_user\",\n    password=\"your_password\",\n    host=\"localhost\",\n    port=\"5432\",\n    database=\"your_database\",\n    default_include_relationships=False  # Set global default\n)\n\n# Or for MySQL\ndb_config.configure_mysql(\n    user=\"your_user\",\n    password=\"your_password\",\n    host=\"localhost\",\n    port=\"3306\",\n    database=\"your_database\",\n    default_include_relationships=False  # Set global default\n)\n```\n\n**Benefits:**\n- **Performance**: Set `default_include_relationships=False` to avoid loading relationships by default, improving query performance\n- **Flexibility**: Still override per method call with explicit `True` or `False` values\n- **Backward Compatible**: Defaults to `True` if not specified, maintaining existing behavior\n\n**Usage Examples:**\n```python\n# With default_include_relationships=False configured:\nusers = await User.all()  # No relationships loaded (faster)\nusers_with_rels = await User.all(include_relationships=True)  # Override to load relationships\n\n# With default_include_relationships=True configured (default behavior):\nusers = await User.all()  # Relationships loaded\nusers_no_rels = await User.all(include_relationships=False)  # Override to skip relationships\n```\n\n### Database Initialization Options\n\n**New in v0.4.1**: The `init_db()` function now supports configurable auto-relationships handling:\n\n```python\n# Default behavior (auto-detect auto-relationships availability)\nawait init_db()\n\n# Force enable auto-relationships (will warn if not available)\nawait init_db(has_auto_relationships=True)\n\n# Force disable auto-relationships\nawait init_db(has_auto_relationships=False)\n\n# Combined with other parameters\nawait init_db(\n    migrate=True,\n    model_classes=[User, Post, Comment],\n    has_auto_relationships=False\n)\n```\n\n**Benefits:**\n- **Control**: Explicitly enable or disable auto-relationships functionality\n- **Reliability**: Auto-relationships errors now issue warnings instead of stopping database initialization\n- **Flexibility**: Can be combined with migration control and specific model classes\n- **Robust**: Database initialization continues even if auto-relationships fail\n\n**Parameter Priority:**\n1. Explicit `has_auto_relationships` parameter (if provided)\n2. Auto-detection of auto-relationships availability (default)\n3. Fallback to available functionality\n\n## Documentation\n\nFor 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/master/DOCS.md) file.\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A simplified SQLModel-based ORM for async database operations",
    "version": "0.4.2",
    "project_urls": {
        "Homepage": "https://github.com/puntorigen/easy-model"
    },
    "split_keywords": [
        "orm",
        " sqlmodel",
        " database",
        " async",
        " postgresql",
        " sqlite",
        " mysql"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4d66327e62c54686f99be594b7fabb858e46f85b44ff2a5d6821b1c07382727d",
                "md5": "4c1beda77ee0e22e7388d0fe662373ef",
                "sha256": "8de20cdf6aeadd84a9cf3c2704a802ed215d07c12598cc528bf462db767100e4"
            },
            "downloads": -1,
            "filename": "async_easy_model-0.4.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4c1beda77ee0e22e7388d0fe662373ef",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 42987,
            "upload_time": "2025-07-26T01:47:02",
            "upload_time_iso_8601": "2025-07-26T01:47:02.769934Z",
            "url": "https://files.pythonhosted.org/packages/4d/66/327e62c54686f99be594b7fabb858e46f85b44ff2a5d6821b1c07382727d/async_easy_model-0.4.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "2980e47bbfc7dfe6110ca41c2b034933f7efc75c06f39183bae95bf85a463d57",
                "md5": "c63fbb12e7dae38f0382b55742240377",
                "sha256": "e1f054728fff4036d3b187e9607582a85826b0f620916b78db700d8cb14be4c1"
            },
            "downloads": -1,
            "filename": "async_easy_model-0.4.2.tar.gz",
            "has_sig": false,
            "md5_digest": "c63fbb12e7dae38f0382b55742240377",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 46049,
            "upload_time": "2025-07-26T01:47:04",
            "upload_time_iso_8601": "2025-07-26T01:47:04.339986Z",
            "url": "https://files.pythonhosted.org/packages/29/80/e47bbfc7dfe6110ca41c2b034933f7efc75c06f39183bae95bf85a463d57/async_easy_model-0.4.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-26 01:47:04",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "puntorigen",
    "github_project": "easy-model",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "async-easy-model"
}
        
Elapsed time: 0.56370s