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

[FEATURE] Pydantic backend to Data Validation #61

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ requires-python = ">=3.7"
dependencies = [
"numpy", # for vector math
"jsonschema", # for spec validation
"pydantic>=2.5",
]
[project.urls]
repository = "https://github.com/sigmf/sigmf-python"
Expand Down
6 changes: 6 additions & 0 deletions sigmf/component/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright: Multiple Authors
#
# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Contains pydantic.BaseModel components for defining the SigMF schema."""
6 changes: 6 additions & 0 deletions sigmf/component/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright: Multiple Authors
#
# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Contains core:extensions pydantic.BaseModel components for defining the SigMF schema."""
24 changes: 24 additions & 0 deletions sigmf/component/extensions/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright: Multiple Authors
#
# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Core:extension definition for SigMFGlobal in Pydantic."""

from pydantic import BaseModel, ConfigDict, Field

from sigmf.component.pydantic_types import VERSION_STR


class SigMFCoreExtension(BaseModel):
"""core:extensions to a Global Object."""

model_config = ConfigDict(extra="forbid")

name: str = Field(..., description="The name of the SigMF extension namespace.", frozen=True)
version: VERSION_STR = Field(
..., description="The version of the extension namespace specification used.", examples=["1.0.0"], frozen=True
)
optional: bool = Field(
..., description="If this field is `true`, the extension is REQUIRED to parse this Recording.", frozen=True
)
66 changes: 66 additions & 0 deletions sigmf/component/geo_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright: Multiple Authors
#
# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later
"""GEOJson Definitions in Pydantic."""

from typing import List, Optional, Dict, Any
from typing_extensions import Literal
from pydantic import (
BaseModel,
Field,
AliasPath,
AliasChoices,
model_serializer,
model_validator,
)


class WGS84Coordinate(BaseModel):
"""Defines latitude, longitude and altitude using the WGS84 coordinate reference system."""

latitude: float = Field(
...,
description="latitude in decimal degrees",
validation_alias=AliasChoices("latitude", AliasPath("coordinates", 0)),
)
longitude: float = Field(
...,
description="longitude in decimal degrees",
validation_alias=AliasChoices("longitude", AliasPath("coordinates", 1)),
)
altitude: Optional[float] = Field(
None,
description="in meters above the WGS84 ellipsoid",
validation_alias=AliasChoices("altitude", AliasPath("coordinates", 2)),
)

@model_serializer
def ser_model(self) -> List[float]:
"""serialize the whole model into a list of floats"""
if self.altitude:
return [self.latitude, self.longitude, self.altitude]
return [self.latitude, self.longitude]


class GeoJSONPoint(BaseModel):
"""GeoJsonPoint object as defined by RFC 7946."""

# for Python 3.8+ - replace "type" with "Literal['point']"
type_: Literal["Point"] = Field(
"Point", alias="type", description="Type of GeoJSON object as per RFC 5870", frozen=True
)
coordinates: WGS84Coordinate = Field(..., description="WGS84 coordinate reference system.")

@model_validator(mode="before")
@classmethod
def convert_coordinate_to_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
"""Converts {"coordinates": [x, y, _z]} to {"coordinates": {"coordinates": [x, y, _z]}}"""
# check that the length is >=2 and < 4
if "coordinates" not in data:
raise KeyError("key `coordinates` not found.")
if not 1 < len(data["coordinates"]) < 4:
raise ValueError(f"`coordinates` length must be 2 or 3 (lat, lon, <alt>), not {len(data['coordinates'])}.")
data["coordinates"] = {"coordinates": data["coordinates"]}
return data
Loading
Loading