Skip to main content

Service objects for Django Ninja using Pydantic validation

Project description

Django Ninja Service Objects

An implementation of the django-service-objects philosophy for Django Ninja with Pydantic validation.

Encapsulate your business logic in reusable, testable service classes.

Installation

pip install django-ninja-service-objects

Add to your Django settings:

# settings.py
INSTALLED_APPS = [
    ...
    'ninja_service_objects',
    ...
]

Usage

from ninja import Schema
from ninja_service_objects import Service

class CreateUserInput(Schema):
    email: str
    name: str

class CreateUserService(Service[CreateUserInput, User]):
    schema = CreateUserInput

    def process(self) -> User:
        return User.objects.create(
            email=self.cleaned_data.email,
            name=self.cleaned_data.name,
        )

    def post_process(self) -> None:
        # Called after successful transaction commit
        send_welcome_email(self.cleaned_data.email)

# In your view
user = CreateUserService.execute({"email": "test@example.com", "name": "Test"})

Decorator-Based Services

For smaller operations, use service_object to get the same validation and transaction handling without defining a service class:

from ninja import Schema
from ninja_service_objects import service_object

class CreateUserInput(Schema):
    email: str
    name: str

def send_welcome_email_after_commit(user: User) -> None:
    send_welcome_email(user.email)

@service_object(post_process=send_welcome_email_after_commit)
def create_user(data: CreateUserInput) -> User:
    return User.objects.create(
        email=data.email,
        name=data.name,
    )

user = create_user({"email": "test@example.com", "name": "Test"})

The decorator validates any function argument annotated with a Pydantic model or Ninja schema. Use db_transaction=False to disable the transaction wrapper, using="other_db" to select a database alias, or post_process=callback to run a side effect after a successful commit. The callback receives the service function result.

Using Pydantic BaseModel with Custom Validators

You can also use Pydantic's BaseModel directly for more complex validation:

from pydantic import BaseModel, EmailStr, field_validator, model_validator
from ninja_service_objects import Service

class RegisterUserInput(BaseModel):
    email: EmailStr
    password: str
    password_confirm: str

    @field_validator("password")
    @classmethod
    def password_min_length(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError("Password must be at least 8 characters")
        return v

    @model_validator(mode="after")
    def passwords_match(self) -> "RegisterUserInput":
        if self.password != self.password_confirm:
            raise ValueError("Passwords do not match")
        return self

class RegisterUserService(Service[RegisterUserInput, User]):
    schema = RegisterUserInput

    def process(self) -> User:
        return User.objects.create_user(
            email=self.cleaned_data.email,
            password=self.cleaned_data.password,
        )

Using ModelField for Django Model Instances

Use ModelField and MultipleModelField to validate Django model instances as service inputs:

from pydantic import BaseModel
from ninja_service_objects import Service, ModelField, MultipleModelField

class TransferOwnershipInput(BaseModel):
    from_user: ModelField[User]
    to_user: ModelField[User]
    posts: MultipleModelField[Post]

class TransferOwnershipService(Service[TransferOwnershipInput, None]):
    schema = TransferOwnershipInput

    def process(self) -> None:
        for post in self.cleaned_data.posts:
            post.author = self.cleaned_data.to_user
            post.save()

By default, ModelField rejects unsaved model instances (objects without a primary key). To allow unsaved instances:

from typing import Annotated

class MyInput(BaseModel):
    user: Annotated[User, ModelField(allow_unsaved=True)]
    items: Annotated[list[Item], MultipleModelField(allow_unsaved=True)]

Features

  • Pydantic validation for inputs
  • Automatic database transaction handling
  • post_process hook for side effects (runs after commit)
  • Decorator-based services for lightweight operations
  • Type-safe with generics support
  • ModelField and MultipleModelField for Django model instance validation

Design Decisions

Why Pydantic instead of Django Forms?

The original django-service-objects uses Django Forms for validation. Since Django Ninja already uses Pydantic for request/response schemas, this library uses Pydantic to:

  • Avoid mixing two validation systems in the same project
  • Reuse your existing Django Ninja schemas as service inputs
  • Get better type hints and IDE support

API Compatibility

We maintain familiar patterns from django-service-objects:

  • cleaned_data - Access validated input data (same naming as Django forms/original library)
  • process() - Override this with your business logic
  • post_process() - Runs after successful transaction commit (for emails, notifications, etc.)
  • execute() - Class method entry point that handles validation and transactions

What's Different

django-service-objects ninja-service-objects
Django Forms validation Pydantic validation
service_clean() method Pydantic validators
Form fields Pydantic BaseModel
is_valid() + execute() Single execute() call

Configuration

Transaction Control

class MyService(Service[MyInput, MyOutput]):
    schema = MyInput
    db_transaction = False  # Disable automatic transaction wrapping
    using = "other_db"      # Use a different database alias

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_ninja_service_objects-0.1.3.tar.gz (9.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_ninja_service_objects-0.1.3-py3-none-any.whl (8.5 kB view details)

Uploaded Python 3

File details

Details for the file django_ninja_service_objects-0.1.3.tar.gz.

File metadata

File hashes

Hashes for django_ninja_service_objects-0.1.3.tar.gz
Algorithm Hash digest
SHA256 182c0c9304f6c10f2d8f3b6845576be7a2e0779ab9280c52a17fed4e26a7cfab
MD5 f2e331dab48e5a30e498ca7865b1e417
BLAKE2b-256 669409385a87ad0388febd4bef533350d23773cda94b39e65d3b6d0d3cdb4d0e

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_ninja_service_objects-0.1.3.tar.gz:

Publisher: publish.yml on bekindsoft/ninja-service-objects

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_ninja_service_objects-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for django_ninja_service_objects-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 f027cc9c0bb48965df69da9d48f62888f42a17863519bd4f52bc027ff4a45287
MD5 5c51c31d4afde1d37c1d9d25c72d7296
BLAKE2b-256 dce58b6241313131967a4fd8c39bfcad1e58bfc377be589c3f9a106e6130ca0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_ninja_service_objects-0.1.3-py3-none-any.whl:

Publisher: publish.yml on bekindsoft/ninja-service-objects

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page