Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
tstorek opened this issue Nov 28, 2023 · 3 comments
Open

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

tstorek opened this issue Nov 28, 2023 · 3 comments
Assignees
Labels
bug Something isn't working

Comments

@tstorek
Copy link
Collaborator

tstorek commented Nov 28, 2023

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))

@tstorek tstorek added the bug Something isn't working label Nov 28, 2023
Copy link

github-actions bot commented Feb 6, 2024

@djs0109 djs0109 assigned djs0109 and unassigned sbanoeon Feb 14, 2024
@djs0109
Copy link
Contributor

djs0109 commented May 7, 2024

@tstorek is this issue already solved by #256 and #258?

@tstorek
Copy link
Collaborator Author

tstorek commented May 8, 2024

@djs0109 I just found that this still must be fixed for metadata. Currently, metadata checks for serialization only. Hence, models fail if a pydanticModel is used, e.g., for Units

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants