Skip to content

Add support serialization of other types defined as pydantic model #213

@tstorek

Description

@tstorek

Describe the bug
Currently, the Datatype check does not allow good inheritance of ContextAttribute, e.g. to define special scenario specific custom models.
Also support for geo:json ist currently missing.
https://fiware-orion.readthedocs.io/en/master/orion-api.html#geospatial-properties-of-entities

To Reproduce
Steps to reproduce the behavior:

  1. Create a child class of ContextAttribute and Overwrite the Vlaue type with a PydanticModel
  2. Create a instance of this model
  3. Try to serialize as json

Expected behavior
To have more convenience it would be nice to allow the definition of Pydantic Models.
Furthermore, currently the DataType validation removes all Pydantic functionality from submodels but Units.
Originally, this was intended to insure compatibility for the standard JSON library that do not define the specific type.
Hovever, the later issue could be simply omitted by standard serialikzing if type is unknown.

Solution (adds support for geojson)

class BaseValueAttribute(BaseModel):
    """
    Model to add the value property to an BaseAttribute Model. The Model
    is represented by a JSON object with the following syntax:


    The attribute value is specified by the value property, whose value may
    be any JSON datatype.
    """
    type: Union[DataType, str] = Field(
        default=DataType.TEXT,
        description="The attribute type represents the NGSI value type of the "
                    "attribute value. Note that FIWARE NGSI has its own type "
                    "system for attribute values, so NGSI value types are not "
                    "the same as JSON types. Allowed characters "
                    "are the ones in the plain ASCII set, except the following "
                    "ones: control characters, whitespace, &, ?, / and #.",
        max_length=256,
        min_length=1,
        regex=FiwareRegex.string_protect.value,  # Make it FIWARE-Safe
    )
    value: Optional[Any] = Field(
        default=None,
        title="Attribute value",
        description="the actual data"
    )

    @validator('value')
    def validate_value_type(cls, value, values):
        """
        Validator for field 'value'
        The validator will try autocast the value based on the given type.
        If `DataType.STRUCTUREDVALUE` is used for type it will also serialize
        pydantic models. With latter operation all additional features of the
        original pydantic model will be dumped.
        If the type is unknown it will check json-serializable.
        """

        type_ = values['type']
        validate_escape_character_free(value)

        if value is not None:
            if type_ == DataType.TEXT:
                if isinstance(value, list):
                    return [str(item) for item in value]
                return str(value)
            if type_ == DataType.BOOLEAN:
                if isinstance(value, list):
                    return [bool(item) for item in value]
                return bool(value)
            if type_ in (DataType.NUMBER, DataType.FLOAT):
                if isinstance(value, list):
                    return [float(item) for item in value]
                return float(value)
            if type_ == DataType.INTEGER:
                if isinstance(value, list):
                    return [int(item) for item in value]
                return int(value)
            if type_ == DataType.DATETIME:
                return value
            if type_ == DataType.ARRAY:
                if isinstance(value, list):
                    return value
                raise TypeError(f"{type(value)} does not match "
                                f"{DataType.ARRAY}")
            if type_ == DataType.STRUCTUREDVALUE:
                if isinstance(value, dict):
                    value = json.dumps(value)
                    return json.loads(value)
                elif isinstance(value, BaseModel):
                    value.json()
                    return value
                raise TypeError(
                    f"{type(value)} does not match " f"{DataType.STRUCTUREDVALUE}"
                )

            if isinstance(value, BaseModel):
                value.json()
                return value

            value = json.dumps(value)
            return json.loads(value)

custom_attribute.py

from pydantic_geojson import (
    PointModel,
    MultiPointModel,
    LineStringModel,
    MultiLineStringModel,
    PolygonModel,
    MultiPolygonModel,
    FeatureModel,
    FeatureCollectionModel,
)

class GeoJsonAttribute(ContextAttribute):
    """
    https://fiware-orion.readthedocs.io/en/master/orion-api.html#geospatial-properties-of-entities
    """

    type: str = Field(default="geo:json", const=True)
    value: Union[
        PointModel,
        MultiPointModel,
        LineStringModel,
        MultiLineStringModel,
        PolygonModel,
        MultiPolygonModel,
        FeatureModel,
        FeatureCollectionModel,
    ]

main.py

if __name__ == "__main__":
    point = PointModel(coordinates=[0, 0])
    print(point.json(indent=2))

    location_attribute = GeoJsonAttribute(value={"type": "Point", "coordinates": [0, 0]})
    print(location_attribute.value.json(indent=2))

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions