Skip to content

marrying pydantic models and tomlkit documents for data validated, style-preserving toml files

License

Notifications You must be signed in to change notification settings

markjoshwel/tomlantic

Repository files navigation

tomlantic

Warning

tomlantic is at 0.2.1 and currently, only i use it myself. it isn't battle tested, so issues may arise.
if you're willing to run into potential bugs and issues, feel free to use it!

marrying pydantic models and tomlkit documents for data validated, style-preserving toml files

uses generics to automagically preserve model types, so you don't lose out on model type information :D

hits

usage

installation

there are three notable methods to use tomlantic with your project:

  1. install from pypi

    pip install tomlantic
  2. install from source

    pip install git+https://github.com/markjoshwel/tomlantic
  3. directly include tomlantic.py in your project

    wget https://raw.githubusercontent.com/markjoshwel/tomlantic/main/tomlantic/tomlantic.py

    tomlantic is a single file module and is free and unencumbered software released into the public domain. so, use it however you please!
    see the licence section for more information.

quickstart

import pydantic
import tomlkit

from tomlantic import ModelBoundTOML


class Project(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(validate_assignment=True)
    name: str
    description: str
    typechecked: bool


class File(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(validate_assignment=True)
    project: Project


toml_doc = tomlkit.parse(
    "[project]\n"
    'name = "tomlantic"\n'
    'description = "marrying pydantic models and tomlkit documents"\n'
    "typechecked = false\n"
)

# where tomlantic comes in handy
toml = ModelBoundTOML(File, toml_doc)

# access the model with .model and change the model freely
model: File = toml.model
model.project.typechecked = True

# dump the model back to a toml document
new_toml_doc = toml.model_dump_toml()
assert new_toml_doc["project"]["typechecked"] == True  # type: ignore
print(new_toml_doc.as_string())

validators

tomlantic also comes with a neat set of validators to use with handling pydantic model fields and/or the toml document itself:

api reference

class tomlantic.ModelBoundTOML

tomlantic's magical glue class for pydantic models and tomlkit documents

will handle pydantic.ValidationErrors into more toml-friendly error messages.
set handle_errors to False to raise the original pydantic.ValidationError

  • attributes:

  • initialisation arguments:

  • raises:

  • methods:

  • usage:

    # instantiate the class
    toml = ModelBoundTOML(YourModel, tomlkit.parse(...))
    # access your model with .model
    toml.model.message = "blowy red vixens fight for a quick jump"
    # dump the model back to a toml document
    toml_document = toml.model_dump_toml()
    # or to a toml string
    toml_string = toml.model_dump_toml().as_string()

def tomlantic.ModelBoundTOML.model_dump_toml()

method that dumps the model as a style-preserved tomlkit.TOMLDocument

def tomlantic.ModelBoundTOML.get_field()

safely retrieve a field by it's location. not recommended for general use due to a lack of type information, but useful when accessing fields programatically

  • signature:

    def get_field(
        self,
        location: Union[str, Tuple[str, ...]],
        default: Any = None,
    ) -> Any: ...
  • arguments:

    • location: Union[str, Tuple[str, ...]]
    • default: Any = None
  • returns the field if it exists, otherwise default

def tomlantic.ModelBoundTOML.set_field()

sets a field by it's location. not recommended for general use due to a lack of type safety, but useful when setting fields programatically

will handle pydantic.ValidationError into more toml-friendly error messages. set handle_errors to False to raise the original pydantic.ValidationError

  • signature:

    def set_field(
        self,
        location: Union[str, Tuple[str, ...]],
        value: Any,
        handle_errors: bool = True,
    ) -> None: ...
  • arguments:

    • location: Union[str, Tuple[str, ...]]
    • value: Any
    • handle_errors: bool = True
  • raises:

def tomlantic.ModelBoundTOML.difference_between_document()

returns a tomlantic.Difference object of the incoming and outgoing fields that were changed between the model and the comparison_document

def tomlantic.ModelBoundTOML.load_from_document()

override fields with those from a new document

by default, this method selectively overrides fields. so fields that have been changed in the model will NOT be overriden by the incoming document

pass False to the selective argument to override ALL fields in the model with the fields of the incoming document

no changes are applied until the incoming document passes all model validations

  • signature:

    def load_from_document(
        self,
        incoming_document: TOMLDocument,
        selective: bool = True,
    ) -> None:
  • arguments:

class tomlantic.Difference

a named tuple for the differences between an outgoing tomlantic.ModelBoundTOML and a tomlkit.TOMLDocument

  • signature:

    class Difference(NamedTuple): ...
  • attributes:

    • incoming_changed_fields: Tuple[str, ...]
    • outgoing_changed_fields: Tuple[str, ...]

def tomlantic.get_toml_field()

safely retrieve a field by it's location. not recommended for general use due to a lack of type information, but useful when accessing fields programatically

  • signature:

    def get_toml_field(
        document: TOMLDocument,
        location: Union[str, Tuple[str, ...]],
        default: Any = None,
    ) -> Any:
  • arguments:

    • document: TOMLDocument
    • location: Union[str, Tuple[str, ...]]
    • default: Any = None
  • returns the field if it exists, otherwise default

def tomlantic.set_toml_field()

safely retrieve a toml documents field by it's location. not recommended for general use due to a lack of type information, but useful when accessing fields programatically

raises KeyError if the field does not exist, or a LookupError if attempting to set a field in a non-table

if handling for errors, handle KeyError before LookupError as LookupError is the base class for KeyError

  • signature:

    def set_toml_field(
        document: TOMLDocument,
        location: Union[str, Tuple[str, ...]],
        value: Any,
    ) -> None:
  • arguments:

    • document: TOMLDocument
    • location: Union[str, Tuple[str, ...]]
    • value: Any

class tomlantic.TomlanticException

base exception class for all tomlantic errors

  • signature:

    class TomlanticException(Exception): ...

the hierarchy of exceptions that inherit this class are:

TomlanticException
├── TOMLValidationError
└── TOMLBaseSingleError
    ├── TOMLAttributeError
    ├── TOMLFrozenError
    ├── TOMLMissingError
    ├── TOMLValueError
    └── TOMLValidationError

class tomlantic.TOMLBaseSingleError

base exception class for single errors, e.g. TOMLMissingError, TOMLValueError

inherits TomlanticException

base exception class for all tomlantic errors

  • signature:

    class TOMLBaseSingleError(TomlanticException): ...
  • attributes:

    • loc: Tuple[str, ...]
      the location of the error in the toml document
      example: ('settings', 'name') = settings.name

    • msg: str
      the error 'message' (if any)

    • pydantic_error: pydantic_core.ErrorDetails
      the original pydantic error, this is what you see in the list of errors when you handle a pydantic.ValidationError

class tomlantic.TOMLAttributeError

error raised when an field does not exist, or is an extra field not in the model and the model has forbidden extra fields

inherits TOMLBaseSingleError, go there for attributes

  • signature:

    class TOMLAttributeError(TOMLBaseSingleError): ...

class tomlantic.TOMLFrozenError

error raised when assigning a value to a frozen field or value

inherits TOMLBaseSingleError, go there for attributes

  • signature:

    class TOMLFrozenError(TOMLBaseSingleError): ...

class tomlantic.TOMLMissingError

raised when a toml document does not contain all the required fields/tables of a model

inherits TOMLBaseSingleError, go there for attributes

  • signature:

    class TOMLMissingError(TOMLBaseSingleError): ...

class tomlantic.TOMLValueError

raised when an item in a toml document is invalid for its respective model field

inherits TOMLBaseSingleError, go there for attributes

  • signature:

    class TOMLValueError(TOMLBaseSingleError): ...

class tomlantic.TOMLValidationError

a toml-friendly version of pydantic.ValidationError, raised when instantiating tomlantic.ModelBoundTOML

inherits TomlanticException

  • signature:

    class TOMLValidationError(TomlanticException): ...
  • attributes:

    • errors: Tuple[TOMLBaseSingleError, ...]
      all validation errors raised when validating the toml document with the model

def tomlantic.validate_to_specific_type()

validate a value's type to be a specific type

  • signature:

    def validate_to_specific_type(v: Any, t: Type[T]) -> T: ...
  • usage:

    validate_to_specific_type("hello", str)  # returns "hello"
    validate_to_specific_type(42, str)       # raises ValueError

def tomlantic.validate_to_multiple_types()

validate a value's type to be in a tuple of specific types

  • signature:

    def validate_to_multiple_types(v: Any, t: Tuple[Type[Ts], ...]) -> Ts:
  • usage:

    validate_to_multiple_types("hello", (str, int))  # returns "hello"
    validate_to_multiple_types(42, (str, int))       # returns 42
    validate_to_multiple_types(42.0, (str, int))     # raises ValueError

def tomlantic.validate_homogeneous_collection()

validate values of a collection to a specific type

  • signature:

    def validate_homogeneous_collection(v: Any, t: Type[T]) -> Collection[T]:
  • usage:

    validate_homogeneous_collection([1, 2, 3], int)    # returns [1, 2, 3]
    validate_homogeneous_collection([1, 2, "3"], int)  # raises ValueError

def tomlantic.validate_heterogeneous_collection()

validate values of a collection to a specific type or a tuple of types

  • signature:

    def validate_heterogeneous_collection(v: Collection[Any], t: Tuple[Type[Ts], ...]) -> Collection[Ts]: ...
  • usage:

    validate_heterogeneous_collection([1, 2, "3"], (int, str))    # returns [1, 2, "3"]
    validate_heterogeneous_collection([1, 2, "3"], (int, float))  # raises ValueError

licence

tomlantic is free and unencumbered software released into the public domain. for more information, please refer to UNLICENCE, https://unlicense.org, or the python module docstring.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>

About

marrying pydantic models and tomlkit documents for data validated, style-preserving toml files

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Languages