Source code for tensorbay.label.attributes

#!/usr/bin/env python3
#
# Copyright 2021 Graviti. Licensed under MIT License.
#

"""The basic concept of the attribute."""

from enum import Enum
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, TypeVar, Union

from tensorbay.utility import EqMixin, NameMixin, ReprMixin, attr, attr_base, camel, common_loads

_AvailaleType = Union[list, bool, int, float, str, None]
_SingleArgType = Union[str, None, Type[_AvailaleType]]
_ArgType = Union[_SingleArgType, Iterable[_SingleArgType]]
_EnumElementType = Union[str, float, bool, None]


class _AttributeType(Enum):
    """All the possible type of the attributes."""

    # pylint: disable=invalid-name
    array = list
    boolean = bool
    integer = int
    number = float
    string = str
    null = None
    instance = "instance"

    @classmethod
    def get_type_name(cls, type_: _SingleArgType) -> str:
        """Return the corresponding enumeration type name of the given string or type.

        Arguments:
            type_: A string or type indicating the attribute type.

        Returns:
            The name of the :class:`_AttributeType` object corresponding to the given type_.

        Raises:
            ValueError: When the input type_ is not supported.

        """
        if type_ in cls.__members__:
            return type_  # type: ignore[return-value]

        try:
            return cls(type_).name
        except ValueError as error:
            raise ValueError(
                "Invalid type_ values for attribute. "
                f"Only support {tuple(cls.__members__.keys())} attribute types"
            ) from error


[docs]class Items(ReprMixin, EqMixin): """The base class of :class:`AttributeInfo`, representing the items of an attribute. When the value type of an attribute is array, the :class:`AttributeInfo` would contain an 'items' field. .. todo:: The format of argument *type_* on the generated web page is incorrect. Arguments: type_: The type of the attribute value, could be a single type or multi-types. The type must be within the followings: - array - boolean - integer - number - string - null - instance enum: All the possible values of an enumeration attribute. minimum: The minimum value of number type attribute. maximum: The maximum value of number type attribute. items: The items inside array type attributes. Attributes: type: The type of the attribute value, could be a single type or multi-types. enum: All the possible values of an enumeration attribute. minimum: The minimum value of number type attribute. maximum: The maximum value of number type attribute. items: The items inside array type attributes. Raises: TypeError: When both ``enum`` and ``type_`` are absent or when ``type_`` is array and ``items`` is absent. Examples: >>> Items(type_="integer", enum=[1, 2, 3, 4, 5], minimum=1, maximum=5) Items( (type): 'integer', (enum): [...], (minimum): 1, (maximum): 5 ) """ _T = TypeVar("_T", bound="Items") _repr_attrs: Tuple[str, ...] = ("type", "enum", "minimum", "maximum", "items") def __init__( self, *, type_: _ArgType = "", enum: Optional[Iterable[_EnumElementType]] = None, minimum: Optional[float] = None, maximum: Optional[float] = None, items: Optional["Items"] = None, ): if type_ != "": self.type, has_array = self._convert_type(type_) if has_array and items: self.items = items if enum: self.enum = list(enum) if minimum is not None: self.minimum = minimum if minimum is not None: self.maximum = maximum @staticmethod def _convert_type(type_: _ArgType) -> Tuple[Union[str, List[str]], bool]: if isinstance(type_, Iterable) and not isinstance(type_, str): converted_types = [_AttributeType.get_type_name(single_type) for single_type in type_] return converted_types, "array" in converted_types converted_type = _AttributeType.get_type_name(type_) return converted_type, converted_type == "array" def _loads(self, contents: Dict[str, Any]) -> None: if "type" in contents: self.type, has_array = self._convert_type(contents["type"]) if has_array: self.items = Items.loads(contents["items"]) if "enum" in contents: self.enum = contents["enum"] if "minimum" in contents: self.minimum = contents["minimum"] if "maximum" in contents: self.maximum = contents["maximum"]
[docs] @classmethod def loads(cls: Type[_T], contents: Dict[str, Any]) -> _T: """Load an Items from a dict containing the items information. Arguments: contents: A dict containing the information of the items. Returns: The loaded :class:`Items` object. Examples: >>> contents = { ... "type": "array", ... "enum": [1, 2, 3, 4, 5], ... "minimum": 1, ... "maximum": 5, ... "items": { ... "enum": [None], ... "type": "null", ... }, ... } >>> Items.loads(contents) Items( (type): 'array', (enum): [...], (minimum): 1, (maximum): 5, (items): Items(...) ) """ return common_loads(cls, contents)
[docs] def dumps(self) -> Dict[str, Any]: """Dumps the information of the items into a dict. Returns: A dict containing all the information of the items. Examples: >>> items = Items(type_="integer", enum=[1, 2, 3, 4, 5], minimum=1, maximum=5) >>> items.dumps() {'type': 'integer', 'enum': [1, 2, 3, 4, 5], 'minimum': 1, 'maximum': 5} """ contents: Dict[str, Any] = {} if hasattr(self, "type"): contents["type"] = self.type if hasattr(self, "items"): contents["items"] = self.items.dumps() if hasattr(self, "enum"): contents["enum"] = self.enum if hasattr(self, "minimum"): contents["minimum"] = self.minimum if hasattr(self, "maximum"): contents["maximum"] = self.maximum return contents
[docs]class AttributeInfo(NameMixin, Items): """This class represents the information of an attribute. It refers to the `Json schema`_ method to describe an attribute. .. todo:: The format of argument *type_* on the generated web page is incorrect. Arguments: name: The name of the attribute. type_: The type of the attribute value, could be a single type or multi-types. The type must be within the followings: - array - boolean - integer - number - string - null - instance enum: All the possible values of an enumeration attribute. minimum: The minimum value of number type attribute. maximum: The maximum value of number type attribute. items: The items inside array type attributes. parent_categories: The parent categories of the attribute. description: The description of the attribute. Attributes: type: The type of the attribute value, could be a single type or multi-types. enum: All the possible values of an enumeration attribute. minimum: The minimum value of number type attribute. maximum: The maximum value of number type attribute. items: The items inside array type attributes. parent_categories: The parent categories of the attribute. description: The description of the attribute. .. _Json schema: https://json-schema.org/ Examples: >>> from tensorbay.label import Items >>> items = Items(type_="integer", enum=[1, 2, 3, 4, 5], minimum=1, maximum=5) >>> AttributeInfo( ... name="example", ... type_="array", ... enum=[1, 2, 3, 4, 5], ... items=items, ... minimum=1, ... maximum=5, ... parent_categories=["parent_category_of_example"], ... description="This is an example", ... ) AttributeInfo("example")( (type): 'array', (enum): [ 1, 2, 3, 4, 5 ], (minimum): 1, (maximum): 5, (items): Items( (type): 'integer', (enum): [...], (minimum): 1, (maximum): 5 ), (parent_categories): [ 'parent_category_of_example' ] ) """ _T = TypeVar("_T", bound="AttributeInfo") _repr_attrs = Items._repr_attrs + ("parent_categories",) _repr_maxlevel = 2 _attrs_base: Items = attr_base(key=None) parent_categories: List[str] = attr(is_dynamic=True, key=camel) def __init__( self, name: str, *, type_: _ArgType = "", enum: Optional[Iterable[_EnumElementType]] = None, minimum: Optional[float] = None, maximum: Optional[float] = None, items: Optional[Items] = None, parent_categories: Union[None, str, Iterable[str]] = None, description: str = "", ): NameMixin.__init__(self, name, description) Items.__init__(self, type_=type_, enum=enum, minimum=minimum, maximum=maximum, items=items) if not parent_categories: return if isinstance(parent_categories, str): self.parent_categories = [parent_categories] else: self.parent_categories = list(parent_categories)
[docs] @classmethod def loads(cls: Type[_T], contents: Dict[str, Any]) -> _T: """Load an AttributeInfo from a dict containing the attribute information. Arguments: contents: A dict containing the information of the attribute. Returns: The loaded :class:`AttributeInfo` object. Examples: >>> contents = { ... "name": "example", ... "type": "array", ... "items": {"type": "boolean"}, ... "description": "This is an example", ... "parentCategories": ["parent_category_of_example"], ... } >>> AttributeInfo.loads(contents) AttributeInfo("example")( (type): 'array', (items): Items( (type): 'boolean', ), (parent_categories): [ 'parent_category_of_example' ] ) """ return common_loads(cls, contents)
[docs] def dumps(self) -> Dict[str, Any]: """Dumps the information of this attribute into a dict. Returns: A dict containing all the information of this attribute. Examples: >>> from tensorbay.label import Items >>> items = Items(type_="integer", minimum=1, maximum=5) >>> attributeinfo = AttributeInfo( ... name="example", ... type_="array", ... items=items, ... parent_categories=["parent_category_of_example"], ... description="This is an example", ... ) >>> attributeinfo.dumps() { 'name': 'example', 'description': 'This is an example', 'type': 'array', 'items': {'type': 'integer', 'minimum': 1, 'maximum': 5}, 'parentCategories': ['parent_category_of_example'], } """ return self._dumps()