Source code for dom_toml.config.fields

#!/usr/bin/env python3
#
#  fields.py
"""
Primitive field types.
"""
#
#  Copyright © 2023 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  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 OR COPYRIGHT HOLDERS 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.
#

# stdlib
from typing import Any, Generic, Type, TypeVar

# 3rd party
import attrs  # nodep

__all__ = (
		"Boolean",
		"FieldType",
		"Integer",
		"Number",
		"String",
		)

_FT = TypeVar("_FT")


[docs]class FieldType(Generic[_FT]): """ Customisable config field type. """ #: The Python type of the object. field_type: Type[_FT] #: String name of the field. field_name: str def __new__(cls, default: _FT) -> _FT: # type: ignore[misc] """ The wrong way to construct an attrs field. :param default: """ # TODO: emit warning return cls.field(default=default)
[docs] @classmethod def validator( # noqa: PRM002 cls: Type["FieldType"], inst: object, attr: attrs.Attribute, value: Any, ) -> None: """ Check if a value conforms to this field's expected datatype. This method is called by attrs. """ if not isinstance(value, cls.field_type): # pragma: no cover err_msg = f"'{attr.name}' must be {cls.field_name} (got {value!r} that is a {value.__class__!r})." raise TypeError(err_msg)
[docs] @classmethod def on_setattr( # noqa: PRM002 cls: Type["FieldType"], inst: object, attr: attrs.Attribute, value: Any, ) -> None: """ Converts values on ``__setattr__``. This method is called by attrs. """ return cls.field_type(value)
[docs] @classmethod def field(cls: Type["FieldType"], default: _FT) -> _FT: """ Construct an attrs field. :param default: """ # Actually returns attr.Attribute, but mypy doesn't like it return attrs.field( default=default, converter=cls.field_type, validator=cls.validator, on_setattr=cls.on_setattr, )
# TODO: sequence types
[docs]class Boolean(FieldType): """ Boolean config field type. """ field_type = bool field_name = "boolean"
[docs]class String(FieldType): """ String config field type. """ field_type = str field_name = "a string"
[docs]class Integer(FieldType): """ Integer config field type. """ field_type = int field_name = "an integer"
[docs]class Number(FieldType): """ Numerical config field type. """ field_type = float field_name = "a number"
[docs] @classmethod def validator( # noqa: PRM002 cls: Type["FieldType"], inst: object, attr: attrs.Attribute, value: Any, ) -> None: """ Check if a value is a number. This method is called by attrs. """ if not isinstance(value, (int, float)): # pragma: no cover err_msg = f"'{attr.name}' must be {cls.field_name} (got {value!r} that is a {value.__class__!r})." raise TypeError(err_msg)