Skip to content

Typing and validation

Adding type hints with Generics¤

New in version 0.92

The Component class optionally accepts type parameters that allow you to specify the types of args, kwargs, slots, and data.

Use this to add type hints to your components, or to validate component inputs.

from django_components import Component

ButtonType = Component[Args, Kwargs, Slots, TemplateData, JsData, CssData]

class Button(ButtonType):
    template_file = "button.html"

    def get_context_data(self, *args, **kwargs):
        ...

The generic parameters are:

  • Args - Positional arguments, must be a Tuple or Any
  • Kwargs - Keyword arguments, must be a TypedDict or Any
  • Slots - Slots, must be a TypedDict or Any
  • TemplateData - Data returned from get_context_data(), must be a TypedDict or Any
  • JsData - Data returned from get_js_data(), must be a TypedDict or Any
  • CssData - Data returned from get_css_data(), must be a TypedDict or Any

You can specify as many or as few of the parameters as you want. The rest will default to Any.

All of these are valid:

ButtonType = Component[Args, Kwargs, Slots, TemplateData, JsData, CssData]
ButtonType = Component[Args, Kwargs, Slots, TemplateData, JsData]
ButtonType = Component[Args, Kwargs, Slots, TemplateData]
ButtonType = Component[Args, Kwargs, Slots]
ButtonType = Component[Args, Kwargs]
ButtonType = Component[Args]

Info

Setting a type parameter to Any will disable typing and validation for that part.

Typing inputs¤

You can use the first 3 parameters to type inputs.

from typing_extensions import NotRequired, Tuple, TypedDict
from django_components import Component, Slot, SlotContent

###########################################
# 1. Define the types
###########################################

# Positional inputs
ButtonArgs = Tuple[str, ...]

# Keyword inputs
class ButtonKwargs(TypedDict):
    name: str
    age: int
    maybe_var: NotRequired[int] # May be ommited

# The data available to the `footer` scoped slot
class ButtonFooterSlotData(TypedDict):
    value: int

# Slots
class ButtonSlots(TypedDict):
    # Use `SlotContent` when you want to allow either `Slot` instance or plain string
    header: SlotContent
    # Use `Slot` for slot functions.
    # The generic specifies the data available to the slot function
    footer: NotRequired[Slot[ButtonFooterSlotData]]

###########################################
# 2. Define the component with those types
###########################################

ButtonType = Component[ButtonArgs, ButtonKwargs, ButtonSlots]

class Button(ButtonType):
    def get_context_data(self, *args, **kwargs):
        ...

When you then call Component.render or Component.render_to_response, you will get type hints:

Button.render(
    # ERROR: Expects a string
    args=(123,),
    kwargs={
        "name": "John",
        # ERROR: Expects an integer
        "age": "invalid",
    },
    slots={
        "header": "...",
        "footer": lambda ctx, slot_data, slot_ref: slot_data.value + 1,
        # ERROR: Unexpected key "foo"
        "foo": "invalid",
    },
)

If you don't want to validate some parts, set them to Any or omit them.

The following will validate only the keyword inputs:

ButtonType = Component[Any, ButtonKwargs]

class Button(ButtonType):
    ...

Typing data¤

You can use the last 3 parameters to type the data returned from get_context_data(), get_js_data(), and get_css_data().

Use this together with the Pydantic integration to have runtime validation of the data.

from typing_extensions import NotRequired, Tuple, TypedDict
from django_components import Component, Slot, SlotContent

###########################################
# 1. Define the types
###########################################

# Data returned from `get_context_data`
class ButtonData(TypedDict):
    data1: str
    data2: int

# Data returned from `get_js_data`
class ButtonJsData(TypedDict):
    js_data1: str
    js_data2: int

# Data returned from `get_css_data`
class ButtonCssData(TypedDict):
    css_data1: str
    css_data2: int

###########################################
# 2. Define the component with those types
###########################################

ButtonType = Component[Any, Any, Any, ButtonData, ButtonJsData, ButtonCssData]

class Button(ButtonType):
    def get_context_data(self, *args, **kwargs):
        ...

    def get_js_data(self, *args, **kwargs):
        ...

    def get_css_data(self, *args, **kwargs):
        ...

When you then call Component.render or Component.render_to_response, the component will raise an error if the data is not valid.

If you don't want to validate some parts, set them to Any.

The following will validate only the data returned from get_js_data:

ButtonType = Component[Any, Any, Any, Any, ButtonJsData]

class Button(ButtonType):
    ...

Passing variadic args and kwargs¤

You may have a function that accepts a variable number of args or kwargs:

def get_context_data(self, *args, **kwargs):
    ...

This is not supported with the typed components.

As a workaround:

  • For a variable number of positional arguments (*args), set a positional argument that accepts a list of values:

    # Tuple of one member of list of strings
    Args = Tuple[List[str]]
    
  • For a variable number of keyword arguments (**kwargs), set a keyword argument that accepts a dictionary of values:

    class Kwargs(TypedDict):
        variable: str
        another: int
        # Pass any extra keys under `extra`
        extra: Dict[str, any]
    

Handling no args or no kwargs¤

To declare that a component accepts no args, kwargs, etc, you can use the EmptyTuple and EmptyDict types:

from django_components import Component, EmptyDict, EmptyTuple

class Button(Component[EmptyTuple, EmptyDict, EmptyDict, EmptyDict, EmptyDict, EmptyDict]):
    ...

Runtime input validation with types¤

Warning

Input validation was part of Django Components from version 0.96 to 0.135.

Since v0.136, input validation is available as a separate extension.

By defualt, when you add types to your component, this will only set up static type hints, but it will not validate the inputs.

To enable input validation, you need to install the djc-ext-pydantic extension:

pip install djc-ext-pydantic

And add the extension to your project:

COMPONENTS = {
    "extensions": [
        "djc_pydantic.PydanticExtension",
    ],
}

djc-ext-pydantic integrates Pydantic for input and data validation. It uses the types defined on the component's class to validate inputs of Django components.

Usage for Python <3.11¤

On Python 3.8-3.10, use typing_extensions

from typing_extensions import TypedDict, NotRequired

Additionally on Python 3.8-3.9, also import annotations:

from __future__ import annotations

Moreover, on 3.10 and less, you may not be able to use NotRequired, and instead you will need to mark either all keys are required, or all keys as optional, using TypeDict's total kwarg.

See PEP-655 for more info.