Skip to main content

pydantic-discriminator

Welcome to pydantic-discriminator! This is a small utility library that adds support for discriminator-based polymorphism to pydantic.

📦Installation

You can install pydantic-discriminator with pip:

pip install pydantic-discriminator

The only requirement is pydantic, which is automatically installed with this library. No additional dependencies will be installed in your environment.

💡What does it do?

😡The problem

Let's say you have a class hierarchy that looks like this:

classDiagram
    class Shape {
        + x: float
        + y: float
    }
    class Circle {
        + radius : float
    }
    class Hexagon {
        + radius : float
    }
    class Rectangle {
        + width : float
        + height : float
    }
    Shape <|-- Circle
    Shape <|-- Hexagon
    Shape <|-- Rectangle
    class Container {
        + shapes : list[Shape]
    }
    Container --> Shape

Let's implement it with pydantic:

class Shape(BaseModel):
    x: float
    y: float

class Circle(Shape):
    radius: float

class Hexagon(Shape):
    radius: float

class Rectangle(Shape):
    width: float
    height: float

class Container(BaseModel):
    shapes: list[Shape]

Now, let's write a program that uses this class hierarchy:

my_data = {
    "shapes": [
        {"x": 0, "y": 0, "radius": 1},  # This is a Circle
        {"x": 1, "y": 2, "radius": 1},  # This is a Hexagon (because I said so)
        {"x": 5, "y": 3, "width": 1, "height": 1},  # This is a Rectangle
    ]
}

cont = Container.model_validate(my_data)
print(cont)
>>> shapes=[Shape(x=0.0, y=0.0), Shape(x=0.0, y=0.0)]

Disappointing, isn't it? We lost all the information about the shapes 😩. This is actually expected behaviour, because pydantic doesn't know that a Shape can be either a Circle, an Hexagon or a Rectangle. We just tell him that it is a list of Shape, and that's it, we get a list of Shape.

😕The "Union" solution

How should we handle this situation? As far as I know, we must sacrifice the Object-Oriented approach and use Union types.

Let's rewrite our class hierarchy, applying the following changes:

  • All classes have a type field that is used as a discriminator, and must be set to a hardcoded value, in the form of a string literal. They must all be different.
  • In the Container class, replace the Shape hint with a Union hint that contains all the possible shapes.
class Shape(BaseModel):
    type: Literal["shape"] = "shape"
    x: float
    y: float

class Circle(Shape):
    type: Literal["circle"] = "circle"
    radius: float

class Hexagon(Shape):
    type: Literal["hexagon"] = "hexagon"
    radius: float

class Rectangle(Shape):
    type: Literal["rectangle"] = "rectangle"
    width: float
    height: float

class Container(BaseModel):
    shapes: list[Circle | Hexagon | Rectangle]

Let's also update the client program:

my_data = {
    "shapes": [
        {"type": "circle", "x": 0, "y": 0, "radius": 1},
        {"type": "hexagon", "x": 1, "y": 2, "radius": 1},
        {"type": "rectangle", "x": 5, "y": 3, "width": 1, "height": 1},
    ]
}

cont = Container.model_validate(my_data)
print(cont)
>>> shapes=[Circle(type='circle', x=0.0, y=0.0, radius=1.0), Hexagon(type='hexagon', x=1.0, y=2.0, radius=1.0), Rectangle(type='rectangle', x=5.0, y=3.0, width=1.0, height=1.0)]

It works! Yay! 🎉

But... something is not right.

The pydantic-discriminator solution

This library provides a solution to this problem by using a modified BaseModel class that can handle this situation. No more Union types, no more monkey patching, no more type checker errors.

Let's go back to the original class hierarchy, but applying the following changes:

  • The Shape class is now a DiscriminatedBaseModel class.
  • All classes have a class keyword argument discriminator that is used as a discriminator, and must be set to a hardcoded value, in the form of a string literal. They must all be different.
from pydantic_discriminator import DiscriminatedBaseModel

class Shape(DiscriminatedBaseModel):
    x: float
    y: float

class Circle(Shape, discriminator="circle"):
    radius: float

class Hexagon(Shape, discriminator="hexagon"):
    radius: float

class Rectangle(Shape, discriminator="rectangle"):
    width: float
    height: float

class Container(BaseModel):
    shapes: list[Shape]
>>> shapes=[Circle(type_='circle', x=0.0, y=0.0, radius=1.0), Hexagon(type_='hexagon', x=1.0, y=2.0, radius=1.0), Rectangle(type_='rectangle', x=5.0, y=3.0, width=1.0, height=1.0)]

It works too! Yay! 🎉

Under the hood, what happens is that the DiscriminatedBaseModel class will automatically add a type_ (aliased to type to avoid potential conflicts with python keywords) field to the model, and whenever a model of the hierarchy is created, it will look for the correct class to instantiate among the registered subclasses, which is the one whose discriminator keyword argument matches the value of the type_ field.

Classes are registered automatically when they are defined in a tree structure, so there is no need to do anything else.

Metadata

Release files for parker-pydantic-discriminator 0.1.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for parker-pydantic-discriminator 0.1.4
File Size Uploaded Provenance
parker_pydantic_discriminator-0.1.4.tar.gz 6.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for parker-pydantic-discriminator 0.1.4
File Interpreter ABI Platform
parker_pydantic_discriminator-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 15.7 kB

Release files / parker_pydantic_discriminator-0.1.4.tar.gz

Download URL parker_pydantic_discriminator-0.1.4.tar.gz
Size 6.8 kB
Tags Source
SHA-256 checksum
How to use checksums
8675d6934c5058552989ccb0904a3befa3ebbc6e692c5f3df33a953c87c4b1cc
BLAKE2b-256 checksum
How to use checksums
3aaa7bbd903d0d229ef60b10af9907a11b2af66271a55d2a28e9f73b7f66ac4d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

Release files / parker_pydantic_discriminator-0.1.4-py3-none-any.whl

Download URL parker_pydantic_discriminator-0.1.4-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
722c737eded5942d52caba9f0ea8569cef0948636321b4ddcc6b3ea8fb9829b6
BLAKE2b-256 checksum
How to use checksums
5a4bb316e5b3975624868ba1c0b7b613c2af94c1bbf09c2369ae41a5899399de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

Security for parker-pydantic-discriminator 0.1.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for parker_pydantic_discriminator-0.1.4.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page