Models

Models in django-api-orm are the primary way to interact with REST API resources. They provide a Django ORM-like interface for working with external APIs.

Defining Models

Basic Model Definition

A model requires a Pydantic schema class and an API endpoint:

from pydantic import BaseModel
from django_api_orm import APIModel

class UserSchema(BaseModel):
    id: int | None = None
    name: str
    email: str
    active: bool = True

class User(APIModel):
    _schema_class = UserSchema
    _endpoint = "/api/v1/users/"

Required Attributes

Every model must define:

  • _schema_class: A Pydantic BaseModel subclass that defines the data structure

  • _endpoint: The API endpoint path (relative to the base URL)

Optional Attributes

You can also define:

  • _id_field: The name of the ID field (default: "id")

  • Custom methods and properties

Model Registration

Before using a model, you must register it with a client:

from django_api_orm import ServiceClient, register_models

with ServiceClient(base_url="https://api.example.com") as client:
    register_models(client, User)

    # Now User.objects is available

Multiple Model Registration

You can register multiple models at once:

register_models(client, User, Post, Comment)

Model Instances

Creating Instances

There are several ways to create model instances:

# Using Manager.create()
user = User.objects.create(name="Alice", email="alice@example.com")

# Using the constructor and save()
user = User(name="Alice", email="alice@example.com", _client=client)
user.save()

# Using Manager.get_or_create()
user, created = User.objects.get_or_create(
    email="alice@example.com",
    defaults={"name": "Alice"}
)

Accessing Attributes

Model instances behave like Pydantic models:

user = User.objects.get(id=1)

# Access attributes
print(user.name)
print(user.email)

# Set attributes
user.name = "Alice Updated"
user.email = "alice.updated@example.com"

Instance Methods

save()

Save the instance to the API:

user = User.objects.get(id=1)
user.name = "New Name"
user.save()

Partial updates are supported:

user.email = "newemail@example.com"
user.save(update_fields=["email"])

delete()

Delete the instance from the API:

user = User.objects.get(id=1)
user.delete()

refresh_from_api()

Refresh the instance with the latest data from the API:

user = User.objects.get(id=1)
# ... some time passes ...
user.refresh_from_api()  # Get latest data

to_dict()

Convert the instance to a dictionary:

user = User.objects.get(id=1)
data = user.to_dict()
# {"id": 1, "name": "Alice", "email": "alice@example.com", "active": True}

# Exclude unset fields
data = user.to_dict(exclude_unset=True)

Class Methods

from_api()

Create an instance from API response data:

data = {"id": 1, "name": "Alice", "email": "alice@example.com"}
user = User.from_api(data, client=client)

get_schema_class()

Get the Pydantic schema class:

schema_class = User.get_schema_class()
# Returns UserSchema

Model Validation

All data is validated using Pydantic before being sent to the API:

from pydantic import EmailStr, Field

class UserSchema(BaseModel):
    id: int | None = None
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    age: int = Field(..., ge=0, le=150)

class User(APIModel):
    _schema_class = UserSchema
    _endpoint = "/api/v1/users/"

# This will raise a validation error
try:
    user = User.objects.create(name="", email="invalid", age=200)
except ValidationError as e:
    print(e)

Async Models

For async operations, use AsyncAPIModel:

from django_api_orm import AsyncAPIModel, AsyncServiceClient, register_async_models

class User(AsyncAPIModel):
    _schema_class = UserSchema
    _endpoint = "/api/v1/users/"

async with AsyncServiceClient(base_url="https://api.example.com") as client:
    register_async_models(client, User)

    # Await async operations
    user = await User.objects.create(name="Alice", email="alice@example.com")
    await user.save()
    await user.delete()

See Async Support for more details on async usage.

Custom Model Behavior

You can add custom methods and properties to your models:

class User(APIModel):
    _schema_class = UserSchema
    _endpoint = "/api/v1/users/"

    def is_admin(self) -> bool:
        """Check if user is an admin."""
        return self.role == "admin"

    @property
    def full_name(self) -> str:
        """Get user's full name."""
        return f"{self.first_name} {self.last_name}"

    def deactivate(self) -> None:
        """Deactivate this user."""
        self.active = False
        self.save(update_fields=["active"])

# Usage
user = User.objects.get(id=1)
if user.is_admin():
    print(f"Admin: {user.full_name}")
user.deactivate()

Best Practices

  1. Use descriptive schema names: UserSchema, PostSchema, etc.

  2. Define optional fields correctly: Use field: type | None = None for optional fields

  3. Use update_fields for partial updates: More efficient and prevents overwriting unchanged data

  4. Handle validation errors: Always wrap create/save operations in try/except blocks

  5. Refresh when needed: Use refresh_from_api() when data might be stale

  6. Custom methods for business logic: Keep API interaction logic in the model

Next Steps