Skip to main content

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

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.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.2

2 files

Supported by

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