Skip to content

drivendataorg/sortedcontainers-pydantic

Repository files navigation

sortedcontainers-pydantic

PyPI conda-forge feedstock Supported Python versions tests codecov

This package extends sortedcontainers, a fast pure-Python library for sorted mutable collections, to work with Pydantic's models, validation, and serialization.

The easiest way to get started is to simply import SortedDict, SortedList, or SortedSet from sortedcontainers_pydantic instead of from sortedcontainers.

from pydantic import BaseModel, TypeAdapter
from sortedcontainers_pydantic import SortedList

class MyModel(BaseModel):
    sorted_list: SortedList[int]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedList([1, 2, 3]))

MyModel.model_validate_json('{"sorted_list": [3, 1, 2]}')
#> MyModel(sorted_list=SortedList([1, 2, 3]))

MyModel(sorted_list=[3, 1, 2]).model_dump_json()
#> '{"sorted_list":[1,2,3]}'

TypeAdapter(SortedList).validate_python([3, 1, 2])
#> SortedList([1, 2, 3])

TypeAdapter(SortedList).validate_json("[3, 1, 2]")
#> SortedList([1, 2, 3])

Reproducible example created by reprexlite v1.0.0

For additional alternative ways to declare types from this library, see the "Usage approaches" section below.

This library also supports key functions to customize sorting behavior. See the "Specifying a key function with Key" section for further details.

Installation

sortedcontainers-pydantic is available on PyPI. You can install it with

pip install sortedcontainers-pydantic

It is also available on conda-forge. You can install it with

conda install sortedcontainers-pydantic --channel conda-forge

Usage approaches

There are three different ways you can use sortedcontainers-pydantic.

1. Import from sortedcontainers_pydantic

The library has subclasses of sortedcontainers's SortedDict, SortedList, and SortedSet with Pydantic's special methods that enable validation and serialization. To use them, simply import classes of the same name from sortedcontainers_pydantic.

from pydantic import BaseModel
from sortedcontainers_pydantic import SortedList

class MyModel(BaseModel):
    sorted_list: SortedList[int]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedList([1, 2, 3]))

MyModel.model_validate_json('{"sorted_list": [3, 1, 2]}')
#> MyModel(sorted_list=SortedList([1, 2, 3]))

2. Use the annotation pattern

New in sortedcontainers-pydantic v2.0.0

The library has special annotation objects SortedDictPydanticAnnotation, SortedListPydanticAnnotation, and SortedSetPydanticAnnotation that can be attached to sortedcontainers's SortedDict, SortedList, and SortedSet, respectively, using typing.Annotated. This implements the annotated pattern supported by Pydantic.

from typing import Annotated

from pydantic import BaseModel
from sortedcontainers import SortedList
from sortedcontainers_pydantic import SortedListPydanticAnnotation

class MyModel(BaseModel):
    sorted_list: Annotated[SortedList[int], SortedListPydanticAnnotation]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedList([1, 2, 3]))

Unlike approach 1, the type being used is the original class from sortedcontainers and not a subclass.

3. Use the wrapper type aliases

New in sortedcontainers-pydantic v2.0.0

You can also use the wrapper types AnnotatedSortedDict, AnnotatedSortedList, or AnnotatedSortedSet. These are simply type aliases implementing approach 2.

from pydantic import BaseModel
from sortedcontainers_pydantic import AnnotatedSortedList

AnnotatedSortedList
#> typing.Annotated[sortedcontainers.sortedlist.SortedList[~_T], <class 'sortedcontainers_pydantic.SortedListPydanticAnnotation'>]

class MyModel(BaseModel):
    sorted_list: AnnotatedSortedList[int]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedList([1, 2, 3]))

Specifying a key function with Key

New in sortedcontainers-pydantic v2.0.0

You can specify a key function to control sorting. The key should be a callable that takes a single argument. It will be run on every element to generate a key for making comparisons. To specify a key, instantiate the Key special annotation object wrapping it, and attach it with typing.Annotated. This works with any of the three approaches.

Example using Key with approach 1

from typing import Annotated

from pydantic import BaseModel
from sortedcontainers_pydantic import Key, SortedList

class MyModel(BaseModel):
    sorted_list: Annotated[SortedList[int], Key(lambda x: -x)]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedKeyList([3, 2, 1], key=<function MyModel.<lambda> at 0x10ae058a0>))

Example using Key with approach 2

from typing import Annotated

from pydantic import BaseModel
from sortedcontainers import SortedList
from sortedcontainers_pydantic import Key, SortedListPydanticAnnotation

class MyModel(BaseModel):
    sorted_list: Annotated[SortedList[int], SortedListPydanticAnnotation, Key(lambda x: -x)]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedKeyList([3, 2, 1], key=<function MyModel.<lambda> at 0x10aa4a520>))

Example using Key with approach 3

from typing import Annotated

from pydantic import BaseModel
from sortedcontainers_pydantic import AnnotatedSortedList, Key

class MyModel(BaseModel):
    sorted_list: Annotated[AnnotatedSortedList[int], Key(lambda x: -x)]

MyModel(sorted_list=[3.0, 1.0, 2.0])
#> MyModel(sorted_list=SortedKeyList([3, 2, 1], key=<function MyModel.<lambda> at 0x10ca65080>))

Reproducible examples created by reprexlite v1.0.0