Package: pydanja
PyDANJAPyDANticJSONAPIJSON:API (or JSONAPI)Suport forPydanticOutputJSONAPIfrom yourFastAPIorPyDanticbased application with very little code.This is a series of classes that can be included into yourPydanticproject that act as a container format for outputting and verifyingJSON:APIcompliant content.This library makes use of BaseModel generics to contain either a single resource or a list of resources as further BaseModels.Installationpip install pydanjaRequirementsThis will support the oldest non-EOL Python (3.8 as of the writing of this document)UsageWith pydanticfrom pydanja import DANJAResource


class TestType(BaseModel):
    """A simple Pydantic BaseModel"""
    # We use an extra resource_id to indicate the ID for JSON:API
    testtype_id: Optional[int] = Field(
        alias="id",
        default=None,
        json_schema_extra={
            "resource_id": True
        }
    )
    name: str
    description: str


resource_container = DANJAResource.from_basemodel(TestType(
    id=1,
    name="Stuff!",
    description="This is desc!"
))
print(resource_container.model_dump_json(indent=2))

# The BaseModel contained resource can be acquired by
resource = resource_container.resourceThis basic example shows aPydanticBaseModel being contained within aDANJAResourceobject. Themodel_dump_jsonwill outputJSON:API:{
  "data": {
    "id": "1",
    "type": "testtype",
    "lid": null,
    "attributes": {
      "testtype_id": 1,
      "name": "Stuff!",
      "description": "This is desc!"
    },
    "relationships": null,
    "links": null,
    "meta": null
  },
  "links": null,
  "meta": null,
  "included": null
}Note that allJSON:APIfields are included in the output of the model dump. If you are using an API framework likeFastAPI, you use theresponse_model_exclude_noneto suppress fields with no values.FastAPI examplefrom typing import Optional, Union
from pydantic import BaseModel, Field, ConfigDict
from fastapi import FastAPI
from pydanja import DANJAResource, DANJAResourceList, DANJAError, danja_openapi
from fastapi.openapi.utils import get_openapi


app = FastAPI()


# Optional: Clear up the OpenAPI documentation by de-cluttering schema
def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title="FastAPI",
        version="2.5.0",
        summary="FastAPI",
        description="FastAPI",
        routes=app.routes,
    )

    app.openapi_schema = danja_openapi(openapi_schema)

    return app.openapi_schema

app.openapi = custom_openapi



# Example BaseModel
class TestType(BaseModel):
    # If we use ID, then we must alias it to avoid clashes with Python
    testtype_id: Optional[int] = Field(
        alias="id",
        default=None,
        json_schema_extra={
            "resource_id": True
        }
    )
    name: str
    description: str


@app.post("/", response_model_exclude_none=True)
async def test_func(payload: DANJAResource[TestType]) -> Union[DANJAResource[TestType], DANJAError]:
    """
    payload will be verified correctly for inbound JSON:API content
    The Union includes a reference to the JSON:API error object that this could throw
    """
    res = TestType(
        id=1,
        name="Stuff!",
        description="This is description!"
    )
    return DANJAResource.from_basemodel(res)

@app.get("/", response_model_exclude_none=True)
async def test_get() -> Union[DANJAResourceList[TestType], DANJAError]:
    values = [
        TestType(id=1, name="One", description="Desc One"),
        TestType(id=2, name="Two", description="Desc Two"),
        TestType(id=3, name="Three", description="Desc Three"),
        TestType(id=4, name="Four", description="Desc Four"),
    ]
    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, includingFastAPIcode in thesrc/examplesdirectory.ContributingThis project usesPDMfor dependency and virtual environment management.It aims to use the lowest supported Python version (3.8 as of the writing of this document)There are currently three build steps in the actions workflow:Unit testLintingType checkingThese can be run through PDM by using:pdm run lintpdm run testpdm run typecheck