Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ jobs:
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
- run: uv sync
- run: uv sync --extra docs
- name: Lint with Ruff
run: uv run ./lint
- name: Test with pytest
run: uv run ./test
- name: Typecheck with ty
run: uv run ./typecheck
- name: Build docs (MkDocs)
run: uv run ./docs-build
- name: Check project version
uses: maybe-hello-world/pyproject-check-version@v4
id: versioncheck
Expand Down
12 changes: 8 additions & 4 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ build:
tools:
python: "3.10"

sphinx:
configuration: docs/conf.py
mkdocs:
configuration: mkdocs.yml

formats:
- pdf
python:
install:
- method: pip
path: .
extra_requirements:
- docs
90 changes: 77 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
![status](https://img.shields.io/pypi/status/pydanja)
![uv-managed](https://img.shields.io/badge/uv-managed-blueviolet)

**Documentation:** [pydanja.readthedocs.io](https://pydanja.readthedocs.io/en/latest/)

# PyDANJA

**PyDAN**<sub>tic</sub> **J**<sub>SON</sub>**A**<sub>PI</sub>


[JSON:API (or JSONAPI)](https://jsonapi.org/format/) Suport for [Pydantic](https://docs.pydantic.dev/latest/)
[JSON:API (or JSONAPI)](https://jsonapi.org/format/) support for [Pydantic](https://docs.pydantic.dev/latest/)

Output [JSONAPI](https://jsonapi.org/format/) from your [FastAPI](https://fastapi.tiangolo.com/) or [PyDantic](https://docs.pydantic.dev/latest/) based application with very little code.
Output [JSONAPI](https://jsonapi.org/format/) from your [FastAPI](https://fastapi.tiangolo.com/) or [Pydantic](https://docs.pydantic.dev/latest/) based application with very little code.

This is a series of classes that can be included into your [Pydantic](https://docs.pydantic.dev/latest/) project that act as a container format for outputting and verifying [JSON:API](https://jsonapi.org/format/) compliant content.

Expand All @@ -25,11 +27,44 @@ This library makes use of BaseModel generics to contain either a single resource

This will support the oldest non-EOL Python (3.10 as of the writing of this document)

## Quickstart

Import the core types and build JSON:API responses from your Pydantic models.

```python
from pydantic import BaseModel, Field
from pydanja import DANJAResource, DANJAResourceList
```

## API Reference

### Primary containers

- `DANJAResource[T]` - single-resource JSON:API document (`data` is one resource)
- `DANJAResourceList[T]` - collection JSON:API document (`data` is a list of resources)
- `DANJASingleResource[T]` - internal resource object used in `data`/`included`
- `DANJAError` and `DANJAErrorList` - JSON:API error payloads
- `DANJALink`, `DANJARelationship`, `DANJAResourceIdentifier`, `DANJASource` - JSON:API support types

### Helper methods

- `DANJAResource.from_basemodel(resource, resource_name=None, resource_id=None)`
- wraps a `BaseModel` as JSON:API
- auto-resolves resource type and id field when not provided
- `DANJAResourceList.from_basemodel_list(resources, resource_name=None, resource_id=None)`
- wraps a list of `BaseModel` instances as JSON:API
- `include_from_basemodels(includes)`
- attaches related resources in `included`
- `resource` and `resources` properties
- return the original wrapped model(s)
- `danja_openapi(schema)`
- rewrites generated OpenAPI schema names to cleaner JSON:API model names

## Usage

With pydantic

```
```python
from pydanja import DANJAResource


Expand Down Expand Up @@ -60,7 +95,7 @@ resource = resource_container.resource

This basic example shows a [Pydantic](https://docs.pydantic.dev/latest/) BaseModel being contained within a `DANJAResource` object. The `model_dump_json` will output [JSON:API](https://jsonapi.org/format/):

```
```json
{
"data": {
"id": "1",
Expand All @@ -83,9 +118,39 @@ This basic example shows a [Pydantic](https://docs.pydantic.dev/latest/) BaseMod

Note that all [JSON:API](https://jsonapi.org/format/) fields are included in the output of the model dump. If you are using an API framework like [FastAPI](https://fastapi.tiangolo.com/), you use the `response_model_exclude_none` to suppress fields with no values.

### FastAPI example
### Pydantic v2 notes

`DANJAResource` and `DANJAResourceList` use wrap validators internally. On current releases this is compatible with Pydantic v2 validation semantics (the validators return the validated model instance), so you should not see the warning:

`A custom validator is returning a value other than self`

`included` resources are intentionally excluded from generic type validation so a response can include related resource types that differ from the top-level `data` resource type.

### More examples

Use an explicit resource type/id field (if auto detection is not desired):

```python
resource = DANJAResource.from_basemodel(
my_model,
resource_name="articles",
resource_id="article_id",
)
```

Add `included` resources:

```python
response = DANJAResource.from_basemodel(article)
response.include_from_basemodels([
{"type": "people", "id": "1", "attributes": {"name": "Ada"}},
{"type": "comments", "id": "99", "attributes": {"body": "Nice post"}},
])
```

### FastAPI example

```python
from typing import Optional, Union
from pydantic import BaseModel, Field, ConfigDict
from fastapi import FastAPI
Expand Down Expand Up @@ -155,14 +220,7 @@ async def test_get() -> Union[DANJAResourceList[TestType], DANJAError]:
return DANJAResourceList.from_basemodel_list(values)
```

This library supports:

* Single resources (`DANJAResource`)
* Lists of resources (`DANJAResourceList`)
* Error objects (`DANJAErrorList`/`DANJAError`)
* Link objects (`DANJALink`)

There are more examples, including [FastAPI](https://fastapi.tiangolo.com/) code in the `src/examples` directory.
There are more runnable examples, including [FastAPI](https://fastapi.tiangolo.com/) usage, in `src/examples`.


### Contributing
Expand All @@ -183,3 +241,9 @@ These can be run through `uv` by using:
* `uv run ./test`
* `uv run ./typecheck`
* `uv run ./all`

Documentation site (MkDocs Material) — install extras and preview locally:

* `uv sync --extra docs`
* `uv run mkdocs serve`
* `uv run ./docs-build` (static output in `site/`)
4 changes: 4 additions & 0 deletions docs-build
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -euo pipefail
uv sync --extra docs
mkdocs build --strict "$@"
20 changes: 20 additions & 0 deletions docs/mkdocs/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# API reference

This page documents the public surface of the `pydanja` package (same symbols as `from pydanja import ...`).

::: pydanja
options:
members:
- DANJALink
- DANJASource
- DANJAResourceIdentifier
- DANJARelationship
- DANJAError
- DANJAErrorList
- DANJASingleResource
- DANJAResource
- DANJAResourceList
- danja_openapi
show_category_heading: true
show_root_heading: true
heading_level: 2
45 changes: 45 additions & 0 deletions docs/mkdocs/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Contributing

The project uses [uv](https://github.com/astral-sh/uv) for dependencies and local workflows.

## Checks

From the repository root:

```bash
uv sync
uv run ./lint
uv run ./test
uv run ./typecheck
```

Or run everything:

```bash
uv run ./all
```

## Documentation site (this site)

Install documentation dependencies and serve locally:

```bash
uv sync --extra docs
uv run mkdocs serve
```

Build a static site (same command CI uses):

```bash
uv run ./docs-build
```

The built output is written to `site/` (gitignored).

## Hosting

Published docs are intended for [Read the Docs](https://readthedocs.org/) using `mkdocs.yml` (see `.readthedocs.yaml`). Maintainers can alternatively deploy the `site/` output to GitHub Pages or another static host.

## Legacy Sphinx files

The `docs/conf.py` tree is an older Sphinx scaffold kept in-repo for reference; the canonical doc build for Read the Docs is MkDocs as configured in `mkdocs.yml`.
15 changes: 15 additions & 0 deletions docs/mkdocs/fastapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# FastAPI

PyDANJA types work well as FastAPI `response_model` / body types: annotate handlers with `DANJAResource[YourModel]` or `DANJAResourceList[YourModel]` so inbound JSON:API payloads are validated.

## Response shaping

Use FastAPI’s model exclusion helpers so empty JSON:API members do not clutter responses, for example `response_model_exclude_none=True` on routes.

## OpenAPI schema cleanup

Large generic models can make OpenAPI noisy. The library provides `danja_openapi` to simplify schema names for DANJA-related components after you build the base OpenAPI dict (see the project README and `src/examples/fastapi.py`).

## Example code

Runnable examples live under `src/examples/` in the repository — start with `fastapi.py` alongside `basic.py` for non-framework usage.
46 changes: 46 additions & 0 deletions docs/mkdocs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Getting started

## Installation

```bash
pip install pydanja
```

Requires **Python 3.10+** and **Pydantic v2** (see `pyproject.toml` on the repository for the declared minimum versions).

## Minimal example

Define a Pydantic model, mark which field should map to the JSON:API resource id (via `json_schema_extra`), then wrap it with `DANJAResource`:

```python
from typing import Optional

from pydantic import BaseModel, Field
from pydanja import DANJAResource


class Article(BaseModel):
article_id: Optional[int] = Field(
alias="id",
default=None,
json_schema_extra={"resource_id": True},
)
title: str


doc = DANJAResource.from_basemodel(
Article(id=1, title="Hello JSON:API"),
)
print(doc.model_dump_json(indent=2))
```

You can read the wrapped model back via `doc.resource`.

## Lists

Use `DANJAResourceList.from_basemodel_list([...])` for collection documents. Access inner models with `.resources`.

## Related reading

- [API reference](api-reference.md) — all exported types and `danja_openapi`
- [FastAPI](fastapi.md) — response models and OpenAPI helpers
14 changes: 14 additions & 0 deletions docs/mkdocs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# PyDANJA

**PyDAN**tic **J**SON**A**PI — lightweight [JSON:API](https://jsonapi.org/format/)-shaped containers for [Pydantic](https://docs.pydantic.dev/latest/) models, with optional helpers for [FastAPI](https://fastapi.tiangolo.com/) and OpenAPI cleanup.

Use this library when you want JSON:API-style request and response documents without committing your entire stack to a single framework-specific JSON:API server implementation.

## Where to go next

- [Getting started](getting-started.md) — install and your first resource document
- [API reference](api-reference.md) — public types and helpers
- [FastAPI](fastapi.md) — routing and OpenAPI integration patterns
- [Contributing](contributing.md) — build these docs locally

The package README on GitHub and PyPI stays the quick overview; this site is the place for structured guides and API detail.
62 changes: 62 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# MkDocs Material — https://squidfunk.github.io/mkdocs-material/
site_name: PyDANJA
site_description: JSON:API support for Pydantic
site_url: https://pydanja.readthedocs.io/
docs_dir: docs/mkdocs

repo_url: https://github.com/Centurix/pydanja
repo_name: Centurix/pydanja
edit_uri: edit/master/docs/mkdocs/

strict: true

theme:
name: material
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
primary: indigo
accent: indigo
toggle:
icon: material/brightness-7
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: indigo
accent: indigo
toggle:
icon: material/brightness-4
name: Switch to light mode
features:
- navigation.instant
- navigation.tracking
- content.code.copy

plugins:
- search
- mkdocstrings:
handlers:
python:
paths: [src]
options:
docstring_style: google
members_order: source
show_root_heading: true
show_symbol_type_heading: true
show_signature_annotations: true

markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences

nav:
- Home: index.md
- Getting started: getting-started.md
- API reference: api-reference.md
- FastAPI: fastapi.md
- Contributing: contributing.md
Loading
Loading