Metadata-Version: 2.4
Name: drf-pydantic-openapi
Version: 0.6.5
Summary: OpenAPI (v3) schema generation via Pydantic models using Django REST Framework.
License-File: LICENSE
Author: iKlotho
Author-email: umutkahrimanedu@gmail.com
Requires-Python: >=3.10,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: django (>=3.2.1)
Requires-Dist: djangorestframework (>=3.13)
Requires-Dist: docstring-parser (>=0.15)
Requires-Dist: jsonref (>=1.1.0,<2.0.0)
Requires-Dist: loguru (>=0.5.3)
Requires-Dist: openapi-pydantic (==0.4.0)
Description-Content-Type: text/markdown

# drf-pydantic-openapi

Generate OpenAPI schema with DRF code using Pydantic models. Supports referencing other services' components.

## Installation

```bash
pip install drf-pydantic-openapi
```

Add to `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    ...
    "rest_framework",
    "drf_pydantic_openapi",
]
```

## Configuration

Add settings to your project's `settings.py`:

```python
DRF_PYDANTIC_OPENAPI = {
    "TITLE": "My API",
    "DESCRIPTION": "API Documentation",
    "API_VERSION": "1.0",
    "OPENAPI_VERSION": "3.1.0",
    "SERVERS": ["https://api.example.com"],
    "INCLUDE_EMPTY_ENDPOINTS": False,
    "REF_SOURCES": {
        "service_B": "http://localhost:8000/openapi/schema.json",
        "service_C": "http://localhost:8001/openapi/schema.json",
    },
    "SECURITY_DEFINITIONS": {
        "BearerAuth": {
            "type": "http",
            "scheme": "bearer",
        }
    },
}
```

## URL Setup

```python
from django.urls import path, re_path
from drf_pydantic_openapi.views import (
    get_schema_view,
    DrfPydanticRedocView,
    DrfPydanticSwaggerView,
    DrfPydanticRapidocView,
)

urlpatterns = [
    re_path(
        "openapi/schema.json",
        get_schema_view(
            api_version="v1",  # optional: filter by API version
            tag_path_regex="/api/v1/",  # optional: regex for extracting tags
            permission_classes=[],  # optional: DRF permission classes
            authentication_classes=[],  # optional: DRF authentication classes
        ).as_view(),
        name="dpo_schema",
    ),
    re_path("openapi/redoc", DrfPydanticRedocView.as_view(), name="redoc"),
    re_path("openapi/swagger", DrfPydanticSwaggerView.as_view(), name="swagger"),
    re_path("openapi/rapidoc", DrfPydanticRapidocView.as_view(), name="rapidoc"),
]
```

Or use the default URLs:

```python
from django.urls import path, include

urlpatterns = [
    path("openapi/", include("drf_pydantic_openapi.urls")),
]
```

Available endpoints with default URLs:
- `/openapi/schema.json` - OpenAPI schema
- `/openapi/docs` - ReDoc documentation

## Using the `@docs` Decorator

Parameters:
- `body`: Request body model
- `errors`: List of error models
- `query`: Query parameters model
- `path`: Path parameters model
- `response`: Response model (overrides return type annotation)

```python
from pydantic import BaseModel, Field
from rest_framework.views import APIView
from drf_pydantic_openapi.utils import docs
from drf_pydantic_openapi.errors import BadRequestError, NotFoundError

class PathParams(BaseModel):
    book_id: str = Field(description="Book ID to retrieve")

class QueryParams(BaseModel):
    include_author: bool | None = Field(default=None, description="Include author details")

class BookResponse(BaseModel):
    id: str
    title: str

class BookView(APIView):
    @docs(errors=[NotFoundError, BadRequestError], path=PathParams, query=QueryParams)
    def get(self, request, book_id: str) -> BookResponse:
        """
        Retrieve a book by ID.

        Returns the book details including title and metadata.
        """
        ...
```

## Multiple Response Types

Use union types to define multiple possible responses:

```python
class SuccessResponse(BaseModel):
    data: dict
    model_config = {"status_code": 200}

class PartialResponse(BaseModel):
    data: dict
    errors: list[str]
    model_config = {"status_code": 207}

class BookView(APIView):
    @docs(response=SuccessResponse | PartialResponse)
    def get(self, request) -> SuccessResponse | PartialResponse:
        ...
```

## Referencing External OpenAPI Components

Use `RefType` to reference models from other services' OpenAPI schemas:

```python
from drf_pydantic_openapi.ref_utils import RefType

# Simple reference
BookModel = RefType("service_B", "BookModel")

class BookView(APIView):
    def get(self, request) -> BookModel:
        ...
```

### Extending Referenced Types

```python
class CustomBookModel(RefType("service_B", "BookModel")):
    # Add new fields
    read_count: int

    model_config = {
        # Remove fields from referenced type
        "ref_exclude": ("author", "internal_id"),
        # Rename fields: (new_name, original_name)
        "ref_rename": (("name", "book_name"),),
    }

class PaginatedBookModel(BaseModel):
    total: int
    next: str | None
    prev: str | None
    data: list[CustomBookModel]

class BookListView(APIView):
    def get(self, request) -> PaginatedBookModel:
        ...
```

## Typed Exception Handler

Configure the exception handler to return typed error responses:

```python
# settings.py
REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "drf_pydantic_openapi.exception_handler.typed_exception_handler"
}
```

Usage:

```python
from drf_pydantic_openapi.errors import BadRequestError, HttpError
from pydantic import BaseModel

# Using built-in errors
class SomeView(APIView):
    @docs(errors=[BadRequestError])
    def post(self, request):
        raise BadRequestError(message="Invalid input")

# Custom error with custom response model
class ValidationErrorResponse(BaseModel):
    message: str
    fields: dict[str, str]

class ValidationError(HttpError):
    status_code = 422
    ResponseModel = ValidationErrorResponse

class SomeView(APIView):
    @docs(errors=[ValidationError])
    def post(self, request):
        raise ValidationError(message="Validation failed", fields={"email": "Invalid format"})
```

Response format:

```json
{
    "detail": {
        "message": "Bad request"
    }
}
```

