From 6821ca68ef457440c2dd4decd83144f3edf8ddbc Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 21:43:04 +0200 Subject: [PATCH 01/20] attr -> pydantic --- pyproject.toml | 4 +- src/czml3/__init__.py | 2 +- src/czml3/base.py | 66 +-- src/czml3/common.py | 26 +- src/czml3/core.py | 104 ++-- src/czml3/enums.py | 49 +- src/czml3/examples/simple.py | 22 +- src/czml3/properties.py | 989 +++++++++++++++++++--------------- src/czml3/types.py | 502 +++++++++++------ src/czml3/utils.py | 70 --- src/czml3/widget.py | 24 +- tests/simple.czml | 4 +- tests/test_document.py | 24 +- tests/test_examples.py | 3 +- tests/test_packet.py | 73 +-- tests/test_properties.py | 229 +++++--- tests/test_rectangle_image.py | 14 +- tests/test_types.py | 79 +-- tests/test_utils.py | 106 ---- tests/test_widget.py | 1 + 20 files changed, 1223 insertions(+), 1168 deletions(-) delete mode 100644 src/czml3/utils.py delete mode 100644 tests/test_utils.py diff --git a/pyproject.toml b/pyproject.toml index 8868181..9360367 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,9 +101,9 @@ classifiers = [ "Topic :: Scientific/Engineering :: Astronomy", ] dependencies = [ - "attrs>=19.2", + "pydantic>=2.10.1", "python-dateutil>=2.7,<3", - "w3lib", + "w3lib" ] dynamic = ["version"] diff --git a/src/czml3/__init__.py b/src/czml3/__init__.py index a5e32ea..b307148 100644 --- a/src/czml3/__init__.py +++ b/src/czml3/__init__.py @@ -1,5 +1,5 @@ from .core import CZML_VERSION, Document, Packet, Preamble -__version__ = "1.0.2" +__version__ = "2.0.0" __all__ = ["Document", "Preamble", "Packet", "CZML_VERSION"] diff --git a/src/czml3/base.py b/src/czml3/base.py index ac1eba2..a797cb8 100644 --- a/src/czml3/base.py +++ b/src/czml3/base.py @@ -1,55 +1,27 @@ -import datetime as dt -import json -import warnings -from enum import Enum -from json import JSONEncoder +from typing import Any -import attr - -from .constants import ISO8601_FORMAT_Z +from pydantic import BaseModel, model_validator NON_DELETE_PROPERTIES = ["id", "delete"] -class CZMLEncoder(JSONEncoder): - def default(self, o): - if isinstance(o, BaseCZMLObject): - return o.to_json() - - elif isinstance(o, Enum): - return o.name - - elif isinstance(o, dt.datetime): - return o.astimezone(dt.timezone.utc).strftime(ISO8601_FORMAT_Z) - - return super().default(o) - - -@attr.s(str=False, frozen=True) -class BaseCZMLObject: - def __str__(self): - return self.dumps(indent=4) - - def dumps(self, *args, **kwargs): - if "cls" in kwargs: - warnings.warn("Ignoring specified cls", UserWarning, stacklevel=2) - - kwargs["cls"] = CZMLEncoder - return json.dumps(self, *args, **kwargs) - - def dump(self, fp, *args, **kwargs): - for chunk in CZMLEncoder(*args, **kwargs).iterencode(self): - fp.write(chunk) +class BaseCZMLObject(BaseModel): + @model_validator(mode="before") + @classmethod + def check_model_before(cls, data: dict[str, Any]) -> Any: + if data is not None and "delete" in data and data["delete"]: + return ( + {"delete": True} + | ({"id": data["id"]} if "id" in data else {}) + | {k: None for k in data if k not in NON_DELETE_PROPERTIES} + ) + return data - def to_json(self): - if getattr(self, "delete", False): - properties_list = NON_DELETE_PROPERTIES - else: - properties_list = list(attr.asdict(self).keys()) + def __str__(self) -> str: + return self.to_json() - obj_dict = {} - for property_name in properties_list: - if getattr(self, property_name, None) is not None: - obj_dict[property_name] = getattr(self, property_name) + def dumps(self) -> str: + return self.model_dump_json(exclude_none=True) - return obj_dict + def to_json(self, *, indent: int = 4) -> str: + return self.model_dump_json(exclude_none=True, indent=indent) diff --git a/src/czml3/common.py b/src/czml3/common.py index 0ff5192..0a3c6c9 100644 --- a/src/czml3/common.py +++ b/src/czml3/common.py @@ -1,28 +1,28 @@ -# noinspection PyPep8Naming -from __future__ import annotations - import datetime as dt -import attr +from pydantic import BaseModel, field_validator from .enums import InterpolationAlgorithms +from .types import format_datetime_like -@attr.s(str=False, frozen=True, kw_only=True) -class Deletable: +class Deletable(BaseModel): """A property whose value may be deleted.""" - delete: bool | None = attr.ib(default=None) + delete: None | bool = None -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) -class Interpolatable: +class Interpolatable(BaseModel): """A property whose value may be determined by interpolating. The interpolation happens over provided time-tagged samples. """ - epoch: dt.datetime | None = attr.ib(default=None) - interpolationAlgorithm: InterpolationAlgorithms | None = attr.ib(default=None) - interpolationDegree: int | None = attr.ib(default=None) + epoch: None | str | dt.datetime = None + interpolationAlgorithm: None | InterpolationAlgorithms = None + interpolationDegree: None | int = None + + @field_validator("epoch") + @classmethod + def check(cls, e): + return format_datetime_like(e) diff --git a/src/czml3/core.py b/src/czml3/core.py index 8add5a7..c57a705 100644 --- a/src/czml3/core.py +++ b/src/czml3/core.py @@ -1,26 +1,47 @@ +from typing import Any from uuid import uuid4 -import attr +from pydantic import Field, model_serializer + +from czml3.types import StringValue from .base import BaseCZMLObject -from .types import Sequence +from .properties import ( + Billboard, + Box, + Clock, + Corridor, + Cylinder, + Ellipse, + Ellipsoid, + Label, + Model, + Orientation, + Path, + Point, + Polygon, + Polyline, + Position, + Rectangle, + Tileset, + ViewFrom, + Wall, +) +from .types import IntervalValue, Sequence, TimeInterval CZML_VERSION = "1.0" -@attr.s(str=False, frozen=True, kw_only=True) class Preamble(BaseCZMLObject): """The preamble packet.""" - id = attr.ib(init=False, default="document") - - version = attr.ib(default=CZML_VERSION) - name = attr.ib(default=None) - description = attr.ib(default=None) - clock = attr.ib(default=None) + id: str = Field(default="document") + version: str = Field(default=CZML_VERSION) + name: None | str = Field(default=None) + description: None | str = Field(default=None) + clock: None | Clock | IntervalValue = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Packet(BaseCZMLObject): """A CZML Packet. @@ -28,37 +49,40 @@ class Packet(BaseCZMLObject): for further information. """ - id = attr.ib(factory=lambda: str(uuid4())) - delete = attr.ib(default=None) - name = attr.ib(default=None) - parent = attr.ib(default=None) - description = attr.ib(default=None) - availability = attr.ib(default=None) - properties = attr.ib(default=None) - position = attr.ib(default=None) - orientation = attr.ib(default=None) - viewFrom = attr.ib(default=None) - billboard = attr.ib(default=None) - box = attr.ib(default=None) - corridor = attr.ib(default=None) - cylinder = attr.ib(default=None) - ellipse = attr.ib(default=None) - ellipsoid = attr.ib(default=None) - label = attr.ib(default=None) - model = attr.ib(default=None) - path = attr.ib(default=None) - point = attr.ib(default=None) - polygon = attr.ib(default=None) - polyline = attr.ib(default=None) - rectangle = attr.ib(default=None) - tileset = attr.ib(default=None) - wall = attr.ib(default=None) + id: str = Field(default=str(uuid4())) + delete: None | bool = Field(default=None) + name: None | str = Field(default=None) + parent: None | str = Field(default=None) + description: None | str | StringValue = Field(default=None) + availability: None | TimeInterval | list[TimeInterval] | Sequence = Field( + default=None + ) + properties: None | Any = Field(default=None) + position: None | Position = Field(default=None) + orientation: None | Orientation = Field(default=None) + viewFrom: None | ViewFrom = Field(default=None) + billboard: None | Billboard = Field(default=None) + box: None | Box = Field(default=None) + corridor: None | Corridor = Field(default=None) + cylinder: None | Cylinder = Field(default=None) + ellipse: None | Ellipse = Field(default=None) + ellipsoid: None | Ellipsoid = Field(default=None) + label: None | Label = Field(default=None) + model: None | Model = Field(default=None) + path: None | Path = Field(default=None) + point: None | Point = Field(default=None) + polygon: None | Polygon = Field(default=None) + polyline: None | Polyline = Field(default=None) + rectangle: None | Rectangle = Field(default=None) + tileset: None | Tileset = Field(default=None) + wall: None | Wall = Field(default=None) -@attr.s(str=False, frozen=True) -class Document(Sequence): +class Document(BaseCZMLObject): """A CZML document, consisting on a list of packets.""" - @property - def packets(self): - return self._values + packets: list[Packet | Preamble] = Field() + + @model_serializer + def custom_serializer(self): + return list(self.packets) diff --git a/src/czml3/enums.py b/src/czml3/enums.py index 201a7a4..997a826 100644 --- a/src/czml3/enums.py +++ b/src/czml3/enums.py @@ -1,7 +1,20 @@ -from enum import Enum, auto +from enum import StrEnum, auto +from typing import Any -class InterpolationAlgorithms(Enum): +class OCaseStrEnum(StrEnum): + """ + StrEnum where enum.auto() returns the original member name, not lower-cased name. + """ + + @staticmethod + def _generate_next_value_( + name: str, start: int, count: int, last_values: list[Any] + ) -> str: + return name + + +class InterpolationAlgorithms(OCaseStrEnum): """The interpolation algorithm to use when interpolating.""" LINEAR = auto() @@ -9,7 +22,7 @@ class InterpolationAlgorithms(Enum): HERMITE = auto() -class ExtrapolationTypes(Enum): +class ExtrapolationTypes(OCaseStrEnum): """The type of extrapolation to perform when a value is requested at a time after any available samples.""" NONE = auto() @@ -17,14 +30,14 @@ class ExtrapolationTypes(Enum): EXTRAPOLATE = auto() -class ReferenceFrames(Enum): +class ReferenceFrames(OCaseStrEnum): """The reference frame in which cartesian positions are specified.""" FIXED = auto() INERTIAL = auto() -class LabelStyles(Enum): +class LabelStyles(OCaseStrEnum): """The style of a label.""" FILL = auto() @@ -32,7 +45,7 @@ class LabelStyles(Enum): FILL_AND_OUTLINE = auto() -class ClockRanges(Enum): +class ClockRanges(OCaseStrEnum): """The behavior of a clock when its current time reaches its start or end time.""" UNBOUNDED = auto() @@ -40,56 +53,62 @@ class ClockRanges(Enum): LOOP_STOP = auto() -class ClockSteps(Enum): +class ClockSteps(OCaseStrEnum): TICK_DEPENDENT = auto() SYSTEM_CLOCK_MULTIPLIER = auto() SYSTEM_CLOCK = auto() -class VerticalOrigins(Enum): +class VerticalOrigins(OCaseStrEnum): BASELINE = auto() BOTTOM = auto() CENTER = auto() TOP = auto() -class HorizontalOrigins(Enum): +class HorizontalOrigins(OCaseStrEnum): LEFT = auto() CENTER = auto() RIGHT = auto() -class HeightReferences(Enum): +class HeightReferences(OCaseStrEnum): NONE = auto() CLAMP_TO_GROUND = auto() RELATIVE_TO_GROUND = auto() -class ColorBlendModes(Enum): +class ColorBlendModes(OCaseStrEnum): HIGHLIGHT = auto() REPLACE = auto() MIX = auto() -class ShadowModes(Enum): +class ShadowModes(OCaseStrEnum): DISABLED = auto() ENABLED = auto() CAST_ONLY = auto() RECEIVE_ONLY = auto() -class ClassificationTypes(Enum): +class ClassificationTypes(OCaseStrEnum): TERRAIN = auto() CESIUM_3D_TILE = auto() BOTH = auto() -class ArcTypes(Enum): +class ArcTypes(OCaseStrEnum): NONE = auto() GEODESIC = auto() RHUMB = auto() -class StripeOrientations(Enum): +class StripeOrientations(OCaseStrEnum): HORIZONTAL = auto() VERTICAL = auto() + + +class CornerTypes(OCaseStrEnum): + ROUNDED = auto() + MITERED = auto() + BEVELED = auto() diff --git a/src/czml3/examples/simple.py b/src/czml3/examples/simple.py index 42dcdd8..c541421 100644 --- a/src/czml3/examples/simple.py +++ b/src/czml3/examples/simple.py @@ -25,7 +25,7 @@ end = dt.datetime(2012, 3, 16, 10, tzinfo=dt.timezone.utc) simple = Document( - [ + packets=[ Preamble( name="simple", clock=IntervalValue( @@ -38,7 +38,7 @@ name="Geoeye1 to ISS", parent=accesses_id, availability=Sequence( - [ + values=[ TimeInterval( start="2012-03-15T10:16:06.97400000000198Z", end="2012-03-15T10:33:59.3549999999959Z", @@ -102,8 +102,8 @@ outlineWidth=2, text="Pennsylvania", verticalOrigin=VerticalOrigins.CENTER, - fillColor=Color.from_list([255, 0, 0]), - outlineColor=Color.from_list([0, 0, 0]), + fillColor=Color(rgba=[255, 0, 0]), + outlineColor=Color(rgba=[0, 0, 0]), ), position=Position( cartesian=[1152255.80150063, -4694317.951340558, 4147335.9067563135] @@ -136,8 +136,8 @@ style=LabelStyles.FILL_AND_OUTLINE, text="AGI", verticalOrigin=VerticalOrigins.CENTER, - fillColor=Color.from_list([0, 255, 255]), - outlineColor=Color.from_list([0, 0, 0]), + fillColor=Color(rgba=[0, 255, 255]), + outlineColor=Color(rgba=[0, 0, 0]), ), position=Position( cartesian=[1216469.9357990976, -4736121.71856379, 4081386.8856866374] @@ -170,14 +170,16 @@ style=LabelStyles.FILL_AND_OUTLINE, text="Geoeye 1", verticalOrigin=VerticalOrigins.CENTER, - fillColor=Color.from_list([0, 255, 0]), - outlineColor=Color.from_list([0, 0, 0]), + fillColor=Color(rgba=[0, 255, 0]), + outlineColor=Color(rgba=[0, 0, 0]), ), path=Path( - show=Sequence([IntervalValue(start=start, end=end, value=True)]), + show=Sequence(values=[IntervalValue(start=start, end=end, value=True)]), width=1, resolution=120, - material=Material(solidColor=SolidColorMaterial.from_list([0, 255, 0])), + material=Material( + solidColor=SolidColorMaterial(color=Color(rgba=[0, 255, 0])) + ), ), position=Position( interpolationAlgorithm=InterpolationAlgorithms.LAGRANGE, diff --git a/src/czml3/properties.py b/src/czml3/properties.py index 5564cf3..3a5bd04 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -1,290 +1,288 @@ from __future__ import annotations -import attr +import datetime as dt +from typing import Any + +from pydantic import ( + BaseModel, + Field, + field_validator, + model_serializer, + model_validator, +) from w3lib.url import is_url, parse_data_uri from .base import BaseCZMLObject from .common import Deletable, Interpolatable from .enums import ( + ArcTypes, + ClassificationTypes, ClockRanges, ClockSteps, + ColorBlendModes, + CornerTypes, + HeightReferences, HorizontalOrigins, LabelStyles, - StripeOrientations, + ShadowModes, VerticalOrigins, ) -from .types import RgbafValue, RgbaValue +from .types import ( + Cartesian2Value, + Cartesian3Value, + CartographicDegreesListValue, + CartographicRadiansListValue, + DistanceDisplayConditionValue, + NearFarScalarValue, + RgbafValue, + RgbaValue, + Sequence, + TimeInterval, + UnitQuaternionValue, + check_reference, + format_datetime_like, + get_color, +) -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) -class HasAlignment: +class HasAlignment(BaseModel): """A property that can be horizontally or vertically aligned.""" - horizontalOrigin: HorizontalOrigins | None = attr.ib(default=None) - verticalOrigin: VerticalOrigins | None = attr.ib(default=None) + horizontalOrigin: None | HorizontalOrigins = Field(default=None) + verticalOrigin: None | VerticalOrigins = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Material(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - solidColor = attr.ib(default=None) - image = attr.ib(default=None) - grid = attr.ib(default=None) - stripe = attr.ib(default=None) - checkerboard = attr.ib(default=None) - polylineOutline = attr.ib(default=None) # NOTE: Not present in documentation + solidColor: None | Color | SolidColorMaterial | str = Field(default=None) + image: None | ImageMaterial | str | Uri = Field(default=None) + grid: None | GridMaterial = Field(default=None) + stripe: None | StripeMaterial = Field(default=None) + checkerboard: None | CheckerboardMaterial = Field(default=None) + polylineOutline: None | PolylineMaterial = Field( + default=None + ) # NOTE: Not present in documentation -@attr.s(str=False, frozen=True, kw_only=True) class PolylineOutline(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - color = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) + color: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | int | float = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineOutlineMaterial(BaseCZMLObject): """A definition of the material wrapper for a polyline outline.""" - polylineOutline = attr.ib(default=None) + polylineOutline: None | PolylineOutline = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineGlow(BaseCZMLObject): """A definition of how a glowing polyline appears.""" - color = attr.ib(default=None) - glowPower = attr.ib(default=None) - taperPower = attr.ib(default=None) + color: None | Color | str = Field(default=None) + glowPower: None | float | int = Field(default=None) + taperPower: None | float | int = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineGlowMaterial(BaseCZMLObject): """A material that fills the surface of a line with a glowing color.""" - polylineGlow = attr.ib(default=None) + polylineGlow: None | PolylineGlow = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineArrow(BaseCZMLObject): """A definition of how a polyline arrow appears.""" - color = attr.ib(default=None) + color: None | Color | str = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineArrowMaterial(BaseCZMLObject): """A material that fills the surface of a line with an arrow.""" - polylineArrow = attr.ib(default=None) + polylineArrow: None | PolylineArrow = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineDash(BaseCZMLObject): """A definition of how a polyline should be dashed with two colors.""" - color = attr.ib(default=None) - gapColor = attr.ib(default=None) - dashLength = attr.ib(default=None) - dashPattern = attr.ib(default=None) + color: None | Color | str = Field(default=None) + gapColor: None | Color | str = Field(default=None) + dashLength: None | float | int = Field(default=None) + dashPattern: None | int = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineDashMaterial(BaseCZMLObject): """A material that provides a how a polyline should be dashed.""" - polylineDash = attr.ib(default=None) + polylineDash: None | PolylineDash = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PolylineMaterial(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - solidColor = attr.ib(default=None) - image = attr.ib(default=None) - grid = attr.ib(default=None) - stripe = attr.ib(default=None) - checkerboard = attr.ib(default=None) - polylineDash = attr.ib(default=None) + solidColor: None | SolidColorMaterial | str = Field(default=None) + image: None | ImageMaterial | str | Uri = Field(default=None) + grid: None | GridMaterial = Field(default=None) + stripe: None | StripeMaterial = Field(default=None) + checkerboard: None | CheckerboardMaterial = Field(default=None) + polylineDash: None | PolylineDashMaterial = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class SolidColorMaterial(BaseCZMLObject): """A material that fills the surface with a solid color.""" - color = attr.ib(default=None) + color: None | Color | str = Field(default=None) - @classmethod - def from_list(cls, color): - return cls(color=Color.from_list(color)) - -@attr.s(str=False, frozen=True, kw_only=True) class GridMaterial(BaseCZMLObject): """A material that fills the surface with a two-dimensional grid.""" - color = attr.ib(default=None) - cellAlpha = attr.ib(default=0.1) - lineCount = attr.ib(default=[8, 8]) - lineThickness = attr.ib(default=[1.0, 1.0]) - lineOffset = attr.ib(default=[0.0, 0.0]) + color: None | Color | str = Field(default=None) + cellAlpha: None | float | int = Field(default=None) + lineCount: None | list[int] = Field(default=None) + lineThickness: None | list[float] | list[int] = Field(default=None) + lineOffset: None | list[float] | list[int] = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class StripeMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - orientation = attr.ib(default=StripeOrientations.HORIZONTAL) - evenColor = attr.ib(default=None) - oddColor = attr.ib(default=None) - offset = attr.ib(default=0.0) - repeat = attr.ib(default=1.0) + orientation: None | int = Field(default=None) + evenColor: None | Color | str = Field(default=None) + oddColor: None | Color | str = Field(default=None) + offset: None | float | int = Field(default=None) + repeat: None | float | int = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class CheckerboardMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - evenColor = attr.ib(default=None) - oddColor = attr.ib(default=None) - repeat = attr.ib(default=None) + evenColor: None | Color | str = Field(default=None) + oddColor: None | Color | str = Field(default=None) + repeat: None | int = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class ImageMaterial(BaseCZMLObject): """A material that fills the surface with an image.""" - image = attr.ib(default=None) - repeat = attr.ib(default=[1, 1]) - color = attr.ib(default=None) - transparent = attr.ib(default=False) + image: None | ImageMaterial | str | Uri = Field(default=None) + repeat: None | list[int] = Field(default=None) + color: None | Color | str = Field(default=None) + transparent: None | bool = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Color(BaseCZMLObject, Interpolatable, Deletable): """A color. The color can optionally vary over time.""" - rgba = attr.ib(default=None) - rgbaf = attr.ib(default=None) + rgba: None | RgbaValue | str | list[float] | list[int] = Field(default=None) + rgbaf: None | RgbafValue | str | list[float] | list[int] = Field(default=None) + @field_validator("rgba", "rgbaf") @classmethod def is_valid(cls, color): - """Determines if the input is a valid color""" - # [R, G, B] or [R, G, B, A] - if ( - isinstance(color, (list, tuple)) - and all(issubclass(type(v), int) for v in color) - and (3 <= len(color) <= 4) - ): - return all(0 <= v <= 255 for v in color) - # [r, g, b] or [r, g, b, a] (float) - elif ( - isinstance(color, (list, tuple)) - and all(issubclass(type(v), float) for v in color) - and (3 <= len(color) <= 4) - ): - return all(0 <= v <= 1 for v in color) - # Hexadecimal RGBA - elif issubclass(type(color), int): - return 0 <= color <= 0xFFFFFFFF - # RGBA string - elif isinstance(color, str): - try: - n = int(color.rsplit("#")[-1], 16) - return 0 <= n <= 0xFFFFFFFF - except ValueError: - return False - return False - - @classmethod - def from_list(cls, color): - if all(issubclass(type(v), int) for v in color): - color = color + [255] if len(color) == 3 else color[:] - - return cls(rgba=RgbaValue(values=color)) - else: - color = color + [1.0] if len(color) == 3 else color[:] + return get_color(color) + + # @classmethod + # def from_list(cls, color): + # if all(issubclass(type(v), int) for v in color): + # color = color + [255] if len(color) == 3 else color[:] + # return cls(rgba=RgbaValue(values=color)) + # else: + # color = color + [1.0] if len(color) == 3 else color[:] + # return cls(rgbaf=RgbafValue(values=color)) + + # @classmethod + # def from_tuple(cls, color): + # return cls.from_list(list(color)) + + # @classmethod + # def from_hex(cls, color): + # if color > 0xFFFFFF: + # values = [ + # (color & 0xFF000000) >> 24, + # (color & 0x00FF0000) >> 16, + # (color & 0x0000FF00) >> 8, + # (color & 0x000000FF) >> 0, + # ] + # else: + # values = [ + # (color & 0xFF0000) >> 16, + # (color & 0x00FF00) >> 8, + # (color & 0x0000FF) >> 0, + # 0xFF, + # ] + + # return cls.from_list(values) + + # @classmethod + # def from_str(cls, color): + # return cls.from_hex(int(color.rsplit("#")[-1], 16)) - return cls(rgbaf=RgbafValue(values=color)) - @classmethod - def from_tuple(cls, color): - return cls.from_list(list(color)) - - @classmethod - def from_hex(cls, color): - if color > 0xFFFFFF: - values = [ - (color & 0xFF000000) >> 24, - (color & 0x00FF0000) >> 16, - (color & 0x0000FF00) >> 8, - (color & 0x000000FF) >> 0, - ] - else: - values = [ - (color & 0xFF0000) >> 16, - (color & 0x00FF00) >> 8, - (color & 0x0000FF) >> 0, - 0xFF, - ] - - return cls.from_list(values) - - @classmethod - def from_str(cls, color): - return cls.from_hex(int(color.rsplit("#")[-1], 16)) - - -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class Position(BaseCZMLObject, Interpolatable, Deletable): """Defines a position. The position can optionally vary over time.""" - referenceFrame = attr.ib(default=None) - cartesian = attr.ib(default=None) - cartographicRadians = attr.ib(default=None) - cartographicDegrees = attr.ib(default=None) - cartesianVelocity = attr.ib(default=None) - reference = attr.ib(default=None) - interval = attr.ib(default=None) - - def __attrs_post_init__(self): - if all( - val is None - for val in ( - self.cartesian, - self.cartographicDegrees, - self.cartographicRadians, - self.cartesianVelocity, - self.reference, + referenceFrame: None | str = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) + cartographicRadians: None | list[float] | list[int] = Field(default=None) + cartographicDegrees: None | list[float] | list[int] = Field(default=None) + cartesianVelocity: None | list[float] | list[int] = Field(default=None) + reference: None | str = Field(default=None) + interval: None | TimeInterval = Field(default=None) + epoch: None | str | dt.datetime = Field(default=None) + + @model_validator(mode="after") + def checks(self): + if self.delete: + return self + if ( + sum( + val is not None + for val in ( + self.cartesian, + self.cartographicDegrees, + self.cartographicRadians, + self.cartesianVelocity, + self.reference, + ) ) + != 1 ): - raise ValueError( + raise TypeError( "One of cartesian, cartographicDegrees, cartographicRadians or reference must be given" ) + return self + + @field_validator("reference") + @classmethod + def check_ref(cls, r): + check_reference(r) + return r + + @field_validator("epoch") + @classmethod + def check_epoch(cls, e): + return format_datetime_like(e) -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class ViewFrom(BaseCZMLObject, Interpolatable, Deletable): """suggested initial camera position offset when tracking this object. ViewFrom can optionally vary over time.""" - cartesian = attr.ib(default=None) - reference = attr.ib(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field() + reference: None | str = Field(default=None) - def __attrs_post_init__(self): - if all(val is None for val in (self.cartesian, self.reference)): - raise ValueError("One of cartesian or reference must be given") + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class Billboard(BaseCZMLObject, HasAlignment): """A billboard, or viewport-aligned image. @@ -292,262 +290,318 @@ class Billboard(BaseCZMLObject, HasAlignment): A billboard is sometimes called a marker. """ - image = attr.ib() - show = attr.ib(default=None) - scale = attr.ib(default=None) - eyeOffset = attr.ib(default=None) - color = attr.ib(default=None) + image: str | Uri = Field() + show: None | bool = Field(default=None) + scale: None | float | int = Field(default=None) + pixelOffset: None | list[float] | list[int] = Field(default=None) + eyeOffset: None | list[float] | list[int] = Field(default=None) + color: None | Color | str = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class EllipsoidRadii(BaseCZMLObject, Interpolatable, Deletable): """The radii of an ellipsoid.""" - cartesian = attr.ib(default=None) - reference = attr.ib(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field() + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class Corridor(BaseCZMLObject): """A corridor , which is a shape defined by a centerline and width that conforms to the curvature of the body shape. It can can optionally be extruded into a volume.""" - positions = attr.ib() - show = attr.ib(default=None) - width = attr.ib() - height = attr.ib(default=None) - heightReference = attr.ib(default=None) - extrudedHeight = attr.ib(default=None) - extrudedHeightReference = attr.ib(default=None) - cornerType = attr.ib(default=None) - granularity = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) - outline = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - shadows = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - classificationType = attr.ib(default=None) - zIndex = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + positions: PositionList | list[int] | list[float] + show: None | bool = Field(default=None) + width: float | int = Field() + height: None | float | int = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + extrudedHeight: None | float | int = Field(default=None) + extrudedHeightReference: None | HeightReference = Field(default=None) + cornerType: None | CornerType = Field(default=None) + granularity: None | float | int = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | int | float = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) + + class Cylinder(BaseCZMLObject): """A cylinder, which is a special cone defined by length, top and bottom radius.""" - length = attr.ib() - show = attr.ib(default=None) - topRadius = attr.ib() - bottomRadius = attr.ib() - heightReference = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) - outline = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - numberOfVerticalLines = attr.ib(default=None) - slices = attr.ib(default=None) - shadows = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + length: float | int = Field() + show: None | bool = Field(default=None) + topRadius: float | int = Field() + bottomRadius: float | int = Field() + heightReference: None | HeightReference = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + numberOfVerticalLines: None | int = Field(default=None) + slices: None | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + + class Ellipse(BaseCZMLObject): """An ellipse, which is a close curve, on or above Earth's surface.""" - semiMajorAxis = attr.ib() - semiMinorAxis = attr.ib() - show = attr.ib(default=None) - height = attr.ib(default=None) - heightReference = attr.ib(default=None) - extrudedHeight = attr.ib(default=None) - extrudedHeightReference = attr.ib(default=None) - rotation = attr.ib(default=None) - stRotation = attr.ib(default=None) - granularity = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) - outline = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - numberOfVerticalLines = attr.ib(default=None) - shadows = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - classificationType = attr.ib(default=None) - zIndex = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + semiMajorAxis: float | int = Field() + semiMinorAxis: float | int = Field() + show: None | bool = Field(default=None) + height: None | float | int = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + extrudedHeight: None | float | int = Field(default=None) + extrudedHeightReference: None | HeightReference = Field(default=None) + rotation: None | float | int = Field(default=None) + stRotation: None | float | int = Field(default=None) + granularity: None | float | int = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + numberOfVerticalLines: None | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) + + class Polygon(BaseCZMLObject): """A polygon, which is a closed figure on the surface of the Earth.""" - positions = attr.ib() - show = attr.ib(default=None) - arcType = attr.ib(default=None) - granularity = attr.ib(default=None) - material = attr.ib(default=None) - shadows = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - classificationType = attr.ib(default=None) - zIndex = attr.ib(default=None) - holes = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outline = attr.ib(default=None) - extrudedHeight = attr.ib(default=None) - perPositionHeight = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + positions: Position | PositionList | list[int] | list[float] = Field() + show: None | bool = Field(default=None) + arcType: None | ArcType = Field(default=None) + granularity: None | float | int = Field(default=None) + material: None | Material | str = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) + holes: None | PositionList | PositionListOfLists | list[int] | list[float] = Field( + default=None + ) # NOTE: not in documentation + outlineColor: None | Color | str = Field(default=None) + outline: None | bool = Field(default=None) + extrudedHeight: None | float | int = Field(default=None) + perPositionHeight: None | bool = Field(default=None) + + class Polyline(BaseCZMLObject): """A polyline, which is a line in the scene composed of multiple segments.""" - positions = attr.ib() - show = attr.ib(default=None) - arcType = attr.ib(default=None) - width = attr.ib(default=None) - granularity = attr.ib(default=None) - material = attr.ib(default=None) - followSurface = attr.ib(default=None) - shadows = attr.ib(default=None) - depthFailMaterial = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - clampToGround = attr.ib(default=None) - classificationType = attr.ib(default=None) - zIndex = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + positions: PositionList = Field() + show: None | bool = Field(default=None) + arcType: None | ArcType = Field(default=None) + width: None | float | int = Field(default=None) + granularity: None | float | int = Field(default=None) + material: ( + None + | PolylineMaterial + | PolylineDashMaterial + | PolylineArrowMaterial + | PolylineGlowMaterial + | PolylineOutlineMaterial + | str + ) = Field(default=None) + followSurface: None | bool = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + depthFailMaterial: ( + None + | PolylineMaterial + | PolylineDashMaterial + | PolylineArrowMaterial + | PolylineGlowMaterial + | PolylineOutlineMaterial + | str + ) = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + clampToGround: None | bool = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) + + class ArcType(BaseCZMLObject, Deletable): """The type of an arc.""" - arcType = attr.ib(default=None) - reference = attr.ib(default=None) + arcType: None | ArcTypes | str = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class ShadowMode(BaseCZMLObject, Deletable): """Whether or not an object casts or receives shadows from each light source when shadows are enabled.""" - shadowMode = attr.ib(default=None) - referenec = attr.ib(default=None) + shadowMode: None | ShadowModes = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class ClassificationType(BaseCZMLObject, Deletable): """Whether a classification affects terrain, 3D Tiles, or both.""" - classificationType = attr.ib(default=None) - reference = attr.ib(default=None) + classificationType: None | ClassificationTypes = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class DistanceDisplayCondition(BaseCZMLObject, Interpolatable, Deletable): """Indicates the visibility of an object based on the distance to the camera.""" - distanceDisplayCondition = attr.ib(default=None) - reference = attr.ib(default=None) + distanceDisplayCondition: None | DistanceDisplayConditionValue = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class PositionListOfLists(BaseCZMLObject, Deletable): """A list of positions.""" - referenceFrame = attr.ib(default=None) - cartesian = attr.ib(default=None) - cartographicRadians = attr.ib(default=None) - cartographicDegrees = attr.ib(default=None) - references = attr.ib(default=None) + referenceFrame: None | str | list[str] = Field(default=None) + cartesian: None | Cartesian3Value = Field(default=None) + cartographicRadians: ( + None | list[float] | list[int] | list[list[float]] | list[list[int]] + ) = Field(default=None) + cartographicDegrees: ( + None | list[float] | list[int] | list[list[float]] | list[list[int]] + ) = Field(default=None) + references: None | str | list[str] = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class PositionList(BaseCZMLObject, Interpolatable, Deletable): """A list of positions.""" - referenceFrame = attr.ib(default=None) - cartesian = attr.ib(default=None) - cartographicRadians = attr.ib(default=None) - cartographicDegrees = attr.ib(default=None) - references = attr.ib(default=None) - interval = attr.ib(default=None) - epoch = attr.ib(default=None) + referenceFrame: None | str | list[str] = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) + cartographicRadians: ( + None | list[float] | list[int] | CartographicRadiansListValue + ) = Field(default=None) + cartographicDegrees: ( + None | list[float] | list[int] | CartographicDegreesListValue + ) = Field(default=None) + references: None | str | list[str] = Field(default=None) + interval: None | TimeInterval = Field(default=None) + epoch: None | str | dt.datetime = Field(default=None) # note: not documented + + @field_validator("epoch") + @classmethod + def check(cls, e): + return format_datetime_like(e) -@attr.s(str=False, frozen=True, kw_only=True) class Ellipsoid(BaseCZMLObject): """A closed quadric surface that is a three-dimensional analogue of an ellipse.""" - radii = attr.ib() - innerRadii = attr.ib(default=None) - minimumClock = attr.ib(default=None) - maximumClock = attr.ib(default=None) - minimumCone = attr.ib(default=None) - maximumCone = attr.ib(default=None) - show = attr.ib(default=None) - heightReference = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) - outline = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - stackPartitions = attr.ib(default=None) - slicePartitions = attr.ib(default=None) - subdivisions = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + radii: EllipsoidRadii + innerRadii: None | EllipsoidRadii = Field(default=None) + minimumClock: None | float | int = Field(default=None) + maximumClock: None | float | int = Field(default=None) + minimumCone: None | float | int = Field(default=None) + maximumCone: None | float | int = Field(default=None) + show: None | bool = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + stackPartitions: None | int = Field(default=None) + slicePartitions: None | int = Field(default=None) + subdivisions: None | int = Field(default=None) + + class Box(BaseCZMLObject): """A box, which is a closed rectangular cuboid.""" - show = attr.ib(default=None) - dimensions = attr.ib(default=None) - heightReference = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) - outline = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - shadows = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) + show: None | bool = Field(default=None) + dimensions: None | BoxDimensions = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class BoxDimensions(BaseCZMLObject, Interpolatable): """The width, depth, and height of a box.""" - cartesian = attr.ib(default=None) - reference = attr.ib(default=None) + cartesian: None | Cartesian3Value = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class Rectangle(BaseCZMLObject, Interpolatable, Deletable): """A cartographic rectangle, which conforms to the curvature of the globe and can be placed on the surface or at altitude and can optionally be extruded into a volume. """ - coordinates = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) + coordinates: None | RectangleCoordinates = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class RectangleCoordinates(BaseCZMLObject, Interpolatable, Deletable): """A set of coordinates describing a cartographic rectangle on the surface of the ellipsoid.""" - reference = attr.ib(default=None) - wsen = attr.ib(default=None) - wsenDegrees = attr.ib(default=None) + reference: None | str = Field(default=None) + wsen: None | list[float] | list[int] = Field(default=None) + wsenDegrees: None | list[float] | list[int] = Field(default=None) - def __attrs_post_init__(self): - if all(val is None for val in (self.wsen, self.wsenDegrees)): - raise ValueError( - "One of cartesian, cartographicDegrees or cartographicRadians must be given" - ) + @model_validator(mode="after") + def checks(self): + if self.delete: + return self + if sum(val is not None for val in (self.wsen, self.wsenDegrees)) != 1: + raise TypeError("One of wsen or wsenDegrees must be given") + return self + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class EyeOffset(BaseCZMLObject, Deletable): """An offset in eye coordinates which can optionally vary over time. @@ -557,20 +611,55 @@ class EyeOffset(BaseCZMLObject, Deletable): """ - cartesian = attr.ib(default=None) - reference = attr.ib(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class HeightReference(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - heightReference = attr.ib(default=None) - reference = attr.ib(default=None) + heightReference: None | HeightReferences = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r + + +class ColorBlendMode(BaseCZMLObject, Deletable): + """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" + + colorBlendMode: None | ColorBlendModes = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r + + +class CornerType(BaseCZMLObject, Deletable): + """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" + + cornerType: None | CornerTypes = Field(default=None) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class Clock(BaseCZMLObject): """Initial settings for a simulated clock when a document is loaded. @@ -578,14 +667,17 @@ class Clock(BaseCZMLObject): """ - currentTime = attr.ib(default=None) - multiplier = attr.ib(default=1.0) - range = attr.ib(default=ClockRanges.LOOP_STOP) - step = attr.ib(default=ClockSteps.SYSTEM_CLOCK_MULTIPLIER) + currentTime: None | str | dt.datetime = Field(default=None) + multiplier: None | float | int = Field(default=None) + range: None | ClockRanges = Field(default=None) + step: None | ClockSteps = Field(default=None) + + @field_validator("currentTime") + @classmethod + def format_time(cls, time): + return format_datetime_like(time) -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class Path(BaseCZMLObject): """A path, which is a polyline defined by the motion of an object over time. @@ -597,61 +689,57 @@ class Path(BaseCZMLObject): """ - show = attr.ib(default=None) - leadTime = attr.ib(default=None) - trailTime = attr.ib(default=None) - width = attr.ib(default=1.0) - resolution = attr.ib(default=60.0) - material = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) + show: None | bool | Sequence = Field(default=None) + leadTime: None | float | int = Field(default=None) + trailTime: None | float | int = Field(default=None) + width: None | float | int = Field(default=None) + resolution: None | float | int = Field(default=None) + material: None | Material | str = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Point(BaseCZMLObject): """A point, or viewport-aligned circle.""" - show = attr.ib(default=None) - pixelSize = attr.ib(default=None) - heightReference = attr.ib(default=None) - color = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - scaleByDistance = attr.ib(default=None) - translucencyByDistance = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - disableDepthTestDistance = attr.ib(default=None) + show: None | bool = Field(default=None) + pixelSize: None | float | int = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + color: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + scaleByDistance: None | NearFarScalar = Field(default=None) + translucencyByDistance: None | NearFarScalar = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + disableDepthTestDistance: None | float | int = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Tileset(BaseCZMLObject): """A 3D Tiles tileset.""" - show = attr.ib(default=None) - uri = attr.ib() - maximumScreenSpaceError = attr.ib(default=None) + uri: str | Uri + show: None | bool = Field(default=None) + maximumScreenSpaceError: None | float | int = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Wall(BaseCZMLObject): """A two-dimensional wall defined as a line strip and optional maximum and minimum heights. It conforms to the curvature of the globe and can be placed along the surface or at altitude. """ - show = attr.ib(default=None) - positions = attr.ib() - minimumHeights = attr.ib(default=None) - maximumHeights = attr.ib(default=None) - granularity = attr.ib(default=None) - fill = attr.ib(default=None) - material = attr.ib(default=None) - outline = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=None) - shadows = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + show: None | bool = Field(default=None) + positions: PositionList = Field() + minimumHeights: None | list[float] | list[int] = Field(default=None) + maximumHeights: None | list[float] | list[int] = Field(default=None) + granularity: None | float | int = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + + class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): """A numeric value which will be linearly interpolated between two values based on an object's distance from the camera, in eye coordinates. @@ -661,29 +749,34 @@ class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): less than the near distance or greater than the far distance, respectively. """ - nearFarScalar = attr.ib(default=None) - reference = attr.ib(default=None) + nearFarScalar: None | list[float] | list[int] | NearFarScalarValue = Field( + default=None + ) + reference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -# noinspection PyPep8Naming -@attr.s(str=False, frozen=True, kw_only=True) class Label(BaseCZMLObject, HasAlignment): """A string of text.""" - show = attr.ib(default=True) - text = attr.ib(default=None) - font = attr.ib(default=None) - style = attr.ib(default=LabelStyles.FILL) - scale = attr.ib(default=None) - showBackground = attr.ib(default=None) - backgroundColor = attr.ib(default=None) - fillColor = attr.ib(default=None) - outlineColor = attr.ib(default=None) - outlineWidth = attr.ib(default=1.0) - pixelOffset = attr.ib(default=None) + show: None | bool = Field(default=None) + text: None | str = Field(default=None) + font: None | str = Field(default=None) + style: None | LabelStyles = Field(default=None) + scale: None | float | int = Field(default=None) + showBackground: None | bool = Field(default=None) + backgroundColor: None | Color | str = Field(default=None) + fillColor: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + pixelOffset: None | float | int | Cartesian2Value = Field(default=None) -@attr.s(str=False, frozen=True, kw_only=True) class Orientation(BaseCZMLObject, Interpolatable, Deletable): """Defines an orientation. @@ -692,50 +785,60 @@ class Orientation(BaseCZMLObject, Interpolatable, Deletable): """ - unitQuaternion = attr.ib(default=None) - reference = attr.ib(default=None) - velocityReference = attr.ib(default=None) + unitQuaternion: None | list[float] | list[int] | UnitQuaternionValue = Field( + default=None + ) + reference: None | str = Field(default=None) + velocityReference: None | str = Field(default=None) + + @field_validator("reference") + @classmethod + def check(cls, r): + check_reference(r) + return r -@attr.s(str=False, frozen=True, kw_only=True) class Model(BaseCZMLObject): """A 3D model.""" - show = attr.ib(default=None) - gltf = attr.ib() - scale = attr.ib(default=None) - minimumPixelSize = attr.ib(default=None) - maximumScale = attr.ib(default=None) - incrementallyLoadTextures = attr.ib(default=None) - runAnimations = attr.ib(default=None) - shadows = attr.ib(default=None) - heightReference = attr.ib(default=None) - silhouetteColor = attr.ib(default=None) - silhouetteSize = attr.ib(default=None) - color = attr.ib(default=None) - colorBlendMode = attr.ib(default=None) - colorBlendAmount = attr.ib(default=None) - distanceDisplayCondition = attr.ib(default=None) - nodeTransformations = attr.ib(default=None) - articulations = attr.ib(default=None) - - -@attr.s(str=False, frozen=True, kw_only=True) + show: None | bool = Field(default=None) + gltf: str = Field() + scale: None | float | int = Field(default=None) + minimumPixelSize: None | float | int = Field(default=None) + maximumScale: None | float | int = Field(default=None) + incrementallyLoadTextures: None | bool = Field(default=None) + runAnimations: None | bool = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + silhouetteColor: None | Color | str = Field(default=None) + silhouetteSize: None | Color | str = Field(default=None) + color: None | Color | str = Field(default=None) + colorBlendMode: None | ColorBlendMode = Field(default=None) + colorBlendAmount: None | float | int = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + nodeTransformations: None | Any = Field(default=None) + articulations: None | Any = Field(default=None) + + class Uri(BaseCZMLObject, Deletable): """A URI value. The URI can optionally vary with time. """ - uri = attr.ib(default=None) + uri: None | str = Field(default=None) - @uri.validator - def _check_uri(self, attribute, value): + @field_validator("uri") + @classmethod + def _check_uri(cls, value: str): + if is_url(value): + return value try: parse_data_uri(value) - except ValueError as e: - if not is_url(value): - raise ValueError("uri must be a URL or a data URI") from e + except ValueError: + raise TypeError("uri must be a URL or a data URI") from None + return value - def to_json(self): + @model_serializer + def custom_serializer(self) -> None | str: return self.uri diff --git a/src/czml3/types.py b/src/czml3/types.py index f41a98e..d6c83bf 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -1,7 +1,14 @@ import datetime as dt +import re +from typing import Any, Self -import attr from dateutil.parser import isoparse as parse_iso_date +from pydantic import ( + Field, + field_validator, + model_serializer, + model_validator, +) from .base import BaseCZMLObject from .constants import ISO8601_FORMAT_Z @@ -9,6 +16,85 @@ TYPE_MAPPING = {bool: "boolean"} +def get_color(color): + """Determines if the input is a valid color""" + if color is None or ( + isinstance(color, list) + and all(issubclass(type(v), (int, float)) for v in color) + and len(color) == 4 + and (all(0 <= v <= 255 for v in color) or all(0 <= v <= 1 for v in color)) + ): + return color + elif ( + isinstance(color, list) + and all(issubclass(type(v), (int, float)) for v in color) + and len(color) == 3 + and all(0 <= v <= 255 for v in color) + ): + return color + [255] + # rgbf or rgbaf + # if ( + # isinstance(color, list) + # and all(issubclass(type(v), (int, float)) for v in color) + # and (3 <= len(color) <= 4) + # and not all(0 <= v <= 1 for v in color) + # ): + # raise TypeError("RGBF or RGBAF values must be between 0 and 1") + elif ( + isinstance(color, list) + and all(issubclass(type(v), (int, float)) for v in color) + and len(color) == 3 + and all(0 <= v <= 1 for v in color) + ): + return color + [1.0] + # Hexadecimal RGBA + # elif issubclass(type(color), int) and not (0 <= color <= 0xFFFFFFFF): + # raise TypeError("Hexadecimal RGBA not valid") + elif ( + issubclass(type(color), int) and (0 <= color <= 0xFFFFFFFF) and color > 0xFFFFFF + ): + return [ + (color & 0xFF000000) >> 24, + (color & 0x00FF0000) >> 16, + (color & 0x0000FF00) >> 8, + (color & 0x000000FF) >> 0, + ] + elif issubclass(type(color), int) and (0 <= color <= 0xFFFFFFFF): + return [ + (color & 0xFF0000) >> 16, + (color & 0x00FF00) >> 8, + (color & 0x0000FF) >> 0, + 0xFF, + ] + # RGBA string + elif isinstance(color, str): + n = int(color.rsplit("#")[-1], 16) + if not (0 <= n <= 0xFFFFFFFF): + raise TypeError("RGBA string not valid") + if n > 0xFFFFFF: + return [ + (n & 0xFF000000) >> 24, + (n & 0x00FF0000) >> 16, + (n & 0x0000FF00) >> 8, + (n & 0x000000FF) >> 0, + ] + else: + return [ + (n & 0xFF0000) >> 16, + (n & 0x00FF00) >> 8, + (n & 0x0000FF) >> 0, + 0xFF, + ] + raise TypeError("Colour type not supported") + + +def check_reference(r): + if re.search(r"^.+#.+$", r) is not None: + raise TypeError( + "Invalid reference string format. Input must be of the form id#property" + ) + + def format_datetime_like(dt_object): if dt_object is None: result = dt_object @@ -22,7 +108,7 @@ def format_datetime_like(dt_object): result = dt_object elif isinstance(dt_object, dt.datetime): - result = dt_object.astimezone(dt.timezone.utc).strftime(ISO8601_FORMAT_Z) + result = dt_object.strftime(ISO8601_FORMAT_Z) else: result = dt_object.strftime(ISO8601_FORMAT_Z) @@ -30,40 +116,16 @@ def format_datetime_like(dt_object): return result -@attr.s(str=False, frozen=True, kw_only=True) -class _TimeTaggedCoords(BaseCZMLObject): - NUM_COORDS: int - property_name: str - - values = attr.ib() - - @values.validator - def _check_values(self, attribute, value): - if not ( - len(value) == self.NUM_COORDS or len(value) % (self.NUM_COORDS + 1) == 0 - ): - raise ValueError( - "Input values must have either 3 or N * 4 values, " - "where N is the number of time-tagged samples." - ) - - def to_json(self): - if hasattr(self, "property_name"): - return {self.property_name: list(self.values)} - return list(self.values) - - -@attr.s(str=False, frozen=True, kw_only=True) class FontValue(BaseCZMLObject): """A font, specified using the same syntax as the CSS "font" property.""" - font = attr.ib(default=None) + font: str = Field() - def to_json(self): + @model_serializer + def custom_serializer(self): return self.font -@attr.s(str=False, frozen=True, kw_only=True) class RgbafValue(BaseCZMLObject): """A color specified as an array of color components [Red, Green, Blue, Alpha] where each component is in the range 0.0-1.0. If the array has four elements, @@ -73,32 +135,35 @@ class RgbafValue(BaseCZMLObject): """ - values = attr.ib() + values: list[float] | list[int] = Field() - @values.validator - def _check_values(self, attribute, value): - if not (len(value) == 4 or len(value) % 5 == 0): - raise ValueError( - "Input values must have either 4 or N * 5 values, " + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 4 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " "where N is the number of time-tagged samples." ) - - if len(value) == 4: - if not all(0 <= val <= 1 for val in value): - raise ValueError("Color values must be floats in the range 0-1.") + if len(self.values) == num_coords: + if not all(0 <= val <= 1 for val in self.values): + raise TypeError("Color values must be floats in the range 0-1.") else: - for i in range(0, len(value), 5): - v = value[i + 1 : i + 5] + for i in range(0, len(self.values), num_coords + 1): + v = self.values[i + 1 : i + num_coords + 1] if not all(0 <= val <= 1 for val in v): - raise ValueError("Color values must be floats in the range 0-1.") + raise TypeError("Color values must be floats in the range 0-1.") + return self - def to_json(self): + @model_serializer + def custom_serializer(self): return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) class RgbaValue(BaseCZMLObject): """A color specified as an array of color components [Red, Green, Blue, Alpha] where each component is in the range 0-255. If the array has four elements, @@ -110,57 +175,60 @@ class RgbaValue(BaseCZMLObject): """ - values = attr.ib() + values: list[float] | list[int] = Field() - @values.validator - def _check_values(self, attribute, value): - if not (len(value) == 4 or len(value) % 5 == 0): - raise ValueError( - "Input values must have either 4 or N * 5 values, " + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 4 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " "where N is the number of time-tagged samples." ) - if len(value) == 4: - if not all(isinstance(val, int) and 0 <= val <= 255 for val in value): - raise ValueError("Color values must be integers in the range 0-255.") + if len(self.values) == num_coords and not all( + isinstance(val, int) and 0 <= val <= 255 for val in self.values + ): + raise TypeError("Color values must be integers in the range 0-255.") else: - for i in range(0, len(value), 5): - v = value[i + 1 : i + 5] + for i in range(0, len(self.values), num_coords + 1): + v = self.values[i + 1 : i + num_coords + 1] if not all(isinstance(val, int) and 0 <= val <= 255 for val in v): - raise ValueError( - "Color values must be integers in the range 0-255." - ) + raise TypeError("Color values must be integers in the range 0-255.") + return self - def to_json(self): - return list(self.values) + @model_serializer + def custom_serializer(self): + return self -@attr.s(str=False, frozen=True, kw_only=True) class ReferenceValue(BaseCZMLObject): """Represents a reference to another property. References can be used to specify that two properties on different objects are in fact, the same property. """ - string = attr.ib(default=None) + string: str = Field() - @string.validator - def _check_string(self, attribute, value): - if not isinstance(value, str): - raise ValueError("Reference must be a string") - if "#" not in value: - raise ValueError( + @field_validator("string") + @classmethod + def _check_string(cls, v): + if "#" not in v: + raise TypeError( "Invalid reference string format. Input must be of the form id#property" ) + return v - def to_json(self): + @model_serializer + def custom_serializer(self): return self.string -@attr.s(str=False, frozen=True, kw_only=True) -class Cartesian3Value(_TimeTaggedCoords): +class Cartesian3Value(BaseCZMLObject): """A three-dimensional Cartesian value specified as [X, Y, Z]. If the values has three elements, the value is constant. @@ -170,11 +238,30 @@ class Cartesian3Value(_TimeTaggedCoords): """ - NUM_COORDS = 3 + values: None | list[Any] = Field(default=None) + + @model_validator(mode="after") + def _check_values(self) -> Self: + if self.values is None: + return self + num_coords = 3 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " + "where N is the number of time-tagged samples." + ) + return self + + @model_serializer + def custom_serializer(self) -> list[Any]: + if self.values is None: + return [] + return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) -class Cartesian2Value(_TimeTaggedCoords): +class Cartesian2Value(BaseCZMLObject): """A two-dimensional Cartesian value specified as [X, Y]. If the values has two elements, the value is constant. @@ -184,12 +271,30 @@ class Cartesian2Value(_TimeTaggedCoords): """ - NUM_COORDS = 2 - property_name = "cartesian2" + values: None | list[Any] = Field(default=None) + @model_validator(mode="after") + def _check_values(self) -> Self: + if self.values is None: + return self + num_coords = 2 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " + "where N is the number of time-tagged samples." + ) + return self + + @model_serializer + def custom_serializer(self): + if self.values is None: + return {} + return {"cartesian2": list(self.values)} -@attr.s(str=False, frozen=True, kw_only=True) -class CartographicRadiansValue(_TimeTaggedCoords): + +class CartographicRadiansValue(BaseCZMLObject): """A geodetic, WGS84 position specified as [Longitude, Latitude, Height]. Longitude and Latitude are in radians and Height is in meters. @@ -200,11 +305,30 @@ class CartographicRadiansValue(_TimeTaggedCoords): """ - NUM_COORDS = 3 + values: None | list[Any] = Field(default=None) + + @model_validator(mode="after") + def _check_values(self) -> Self: + if self.values is None: + return self + num_coords = 3 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " + "where N is the number of time-tagged samples." + ) + return self + + @model_serializer + def custom_serializer(self): + if self.values is None: + return [] + return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) -class CartographicDegreesValue(_TimeTaggedCoords): +class CartographicDegreesValue(BaseCZMLObject): """A geodetic, WGS84 position specified as [Longitude, Latitude, Height]. Longitude and Latitude are in degrees and Height is in meters. @@ -215,59 +339,82 @@ class CartographicDegreesValue(_TimeTaggedCoords): """ - NUM_COORDS = 3 + values: None | list[Any] = Field(default=None) + + @model_validator(mode="after") + def _check_values(self) -> Self: + if self.values is None: + return self + num_coords = 3 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " + "where N is the number of time-tagged samples." + ) + return self + + @model_serializer + def custom_serializer(self) -> list[Any]: + if self.values is None: + return [] + return self.values -@attr.s(str=False, frozen=True, kw_only=True) class StringValue(BaseCZMLObject): """A string value. The string can optionally vary with time. """ - string = attr.ib(default=None) + string: str = Field() - def to_json(self): + @model_serializer + def custom_serializer(self) -> str: return self.string -@attr.s(str=False, frozen=True, kw_only=True) class CartographicRadiansListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in radians and Height is in meters.""" - values = attr.ib() + values: list[float] | list[int] = Field() - @values.validator - def _check_values(self, attribute, value): - if len(value) % 3 != 0: - raise ValueError( - "Invalid values. Input values should be arrays of size 3 * N" + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 3 + if len(self.values) % num_coords != 0: + raise TypeError( + f"Invalid values. Input values should be arrays of size {num_coords} * N" ) + return self - def to_json(self): + @model_serializer + def custom_serializer(self): return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) class CartographicDegreesListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in degrees and Height is in meters.""" - values = attr.ib() + values: list[float] | list[int] = Field() - @values.validator - def _check_values(self, attribute, value): - if len(value) % 3 != 0: - raise ValueError( - "Invalid values. Input values should be arrays of size 3 * N" + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 3 + if len(self.values) % num_coords != 0: + raise TypeError( + f"Invalid values. Input values should be arrays of size {num_coords} * N" ) + return self - def to_json(self): + @model_serializer + def custom_serializer(self): return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) class DistanceDisplayConditionValue(BaseCZMLObject): """A value indicating the visibility of an object based on the distance to the camera, specified as two values [NearDistance, FarDistance]. If the array has two elements, the value is constant. If it has three or more elements, @@ -275,20 +422,22 @@ class DistanceDisplayConditionValue(BaseCZMLObject): where Time is an ISO 8601 date and time string or seconds since epoch. """ - values = attr.ib(default=None) + values: list[float] | list[int] = Field() - @values.validator - def _check_values(self, attribute, value): - if len(value) != 2 and len(value) % 3 != 0: - raise ValueError( - "Invalid values. Input values should be arrays of size either 2 or 3 * N" + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 2 + if len(self.values) != num_coords and len(self.values) % (num_coords + 1) != 0: + raise TypeError( + f"Invalid values. Input values should be arrays of size either {num_coords} or {num_coords + 1} * N" ) + return self - def to_json(self): + @model_serializer + def custom_serializer(self): return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) class NearFarScalarValue(BaseCZMLObject): """A near-far scalar value specified as four values [NearDistance, NearValue, FarDistance, FarValue]. @@ -297,76 +446,81 @@ class NearFarScalarValue(BaseCZMLObject): FarDistance, FarValue, ...], where Time is an ISO 8601 date and time string or seconds since epoch. """ - values = attr.ib(default=None) + values: list[float] | list[int] = Field() - @values.validator - def _check_values(self, attribute, value): - if not (len(value) == 4 or len(value) % 5 == 0): - raise ValueError( - "Input values must have either 4 or N * 5 values, " + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 4 + if not ( + len(self.values) == num_coords or len(self.values) % (num_coords + 1) == 0 + ): + raise TypeError( + f"Input values must have either {num_coords} or N * {num_coords + 1} values, " "where N is the number of time-tagged samples." ) + return self - def to_json(self): + @model_serializer + def custom_serializer(self): return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) class TimeInterval(BaseCZMLObject): """A time interval, specified in ISO8601 interval format.""" - _start = attr.ib(default=None) - _end = attr.ib(default=None) - - def to_json(self): - if self._start is None: - start = "0000-00-00T00:00:00Z" - else: - start = format_datetime_like(self._start) + start: str | dt.datetime = Field(default="0001-01-01T00:00:00Z") + end: str | dt.datetime = Field(default="9999-12-31T23:59:59Z") - if self._end is None: - end = "9999-12-31T24:00:00Z" - else: - end = format_datetime_like(self._end) + @field_validator("start", "end") + @classmethod + def format_time(cls, time): + return format_datetime_like(time) - return f"{start}/{end}" + @model_serializer + def custom_serializer(self) -> str: + return f"{self.start}/{self.end}" -@attr.s(str=False, frozen=True, kw_only=True) class IntervalValue(BaseCZMLObject): """Value over some interval.""" - _start = attr.ib() - _end = attr.ib() - _value = attr.ib() + start: str | dt.datetime = Field() + end: str | dt.datetime = Field() + value: Any = Field(default=None) - def to_json(self): - obj_dict = {"interval": TimeInterval(start=self._start, end=self._end)} + @model_serializer + def custom_serializer(self) -> dict[str, Any]: + obj_dict = { + "interval": TimeInterval(start=self.start, end=self.end).model_dump( + exclude_none=True + ) + } - if isinstance(self._value, BaseCZMLObject): - obj_dict.update(**self._value.to_json()) - elif isinstance(self._value, list): - for value in self._value: - obj_dict.update(**value.to_json()) + if isinstance(self.value, BaseCZMLObject): + obj_dict.update(self.value.model_dump(exclude_none=True)) + elif isinstance(self.value, list) and all( + isinstance(v, BaseCZMLObject) for v in self.value + ): + for value in self.value: + obj_dict.update(value.model_dump()) else: - key = TYPE_MAPPING[type(self._value)] - obj_dict[key] = self._value + key = TYPE_MAPPING[type(self.value)] + obj_dict[key] = self.value return obj_dict -@attr.s(str=False, frozen=True) class Sequence(BaseCZMLObject): """Sequence, list, array of objects.""" - _values = attr.ib() + values: list[Any] = Field() - def to_json(self): - return list(self._values) + @model_serializer + def custom_serializer(self) -> list[Any]: + return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) -class UnitQuaternionValue(_TimeTaggedCoords): +class UnitQuaternionValue(BaseCZMLObject): """A set of 4-dimensional coordinates used to represent rotation in 3-dimensional space. It's specified as [X, Y, Z, W]. If the array has four elements, the value is constant. @@ -376,45 +530,39 @@ class UnitQuaternionValue(_TimeTaggedCoords): """ - NUM_COORDS = 4 + values: list[float] | list[int] = Field() + + @model_validator(mode="after") + def _check_values(self) -> Self: + num_coords = 4 + if len(self.values) % num_coords != 0: + raise TypeError( + f"Invalid values. Input values should be arrays of size {num_coords} * N" + ) + return self + + @model_serializer + def custom_serializer(self): + return list(self.values) -@attr.s(str=False, frozen=True, kw_only=True) class EpochValue(BaseCZMLObject): """A value representing a time epoch.""" - _value = attr.ib() + value: str | dt.datetime = Field() - @_value.validator - def _check_epoch(self, attribute, value): - if not isinstance(value, (str, dt.datetime)): - raise ValueError("Epoch must be a string or a datetime object.") + @model_serializer + def custom_serializer(self): + return {"epoch": format_datetime_like(self.value)} - def to_json(self): - return {"epoch": format_datetime_like(self._value)} - -@attr.s(str=False, frozen=True, kw_only=True) class NumberValue(BaseCZMLObject): """A single number, or a list of number pairs signifying the time and representative value.""" - values = attr.ib() - - @values.validator - def _check_values(self, attribute, value): - if isinstance(value, list): - if not all(isinstance(val, (int, float)) for val in value): - raise ValueError("Values must be integers or floats.") - if len(value) % 2 != 0: - raise ValueError( - "Values must be a list of number pairs signifying the time and representative value." - ) + values: int | float | list[float] | list[int] = Field() - elif not isinstance(value, (int, float)): - raise ValueError("Values must be integers or floats.") - - def to_json(self): + @model_serializer + def custom_serializer(self): if isinstance(self.values, (int, float)): return {"number": self.values} - return {"number": list(self.values)} diff --git a/src/czml3/utils.py b/src/czml3/utils.py deleted file mode 100644 index 90a4809..0000000 --- a/src/czml3/utils.py +++ /dev/null @@ -1,70 +0,0 @@ -from functools import reduce - -from .properties import Color -from .types import RgbafValue, RgbaValue - - -def get_color_list(timestamps, colors, rgbaf=False): - """ - Given a list of valid colors (rgb/rgba tuples, shorthand hex string or integer representation) and a list of - time-stamps, create a Color object with rgba/rgbaf values of the form: [time_0, r_0, g_0, b_0,..., time_k, r_k, - g_k, b_k] - - Parameters - ---------- - :param list(str) timestamps: The list of the timestamps (ISO 8601 date or seconds since epoch) - :param str/int/list colors : A list of valid colors - :param bool rgbaf: If set to True, returns rgbaf values, else return rgba values - """ - # Check if colors is a valid list of colors - if all(Color.is_valid(v) for v in colors): - color_lst = list(map(get_color, colors)) - else: - raise ValueError("Invalid input") - - # Quick function to convert between rgba-rgbaf easier - def color_r(c): - if rgbaf: - return ( - c.rgbaf.values if c.rgbaf else [float(x / 255) for x in c.rgba.values] - ) - else: - return ( - c.rgba.values - if c.rgba - else [int(round(x * 255)) for x in c.rgbaf.values] - ) - - # Get combined list of timestamps and colors - time_colr = [[time] + color_r(c) for time, c in zip(timestamps, color_lst)] - # Flatten list - time_colr = reduce(lambda x, y: x + y, time_colr) - - if rgbaf: - return Color(rgbaf=RgbafValue(values=time_colr)) - else: - return Color(rgba=RgbaValue(values=time_colr)) - - -def get_color(color): - """ - A helper function to make color setting more versatile. What the ``color`` parameter determines depends on - its type. - - Parameters - ---------- - - :param str/int/list color: Depending on the type, ``color`` can be either a hexadecimal rgb/rgba color value or - a tuple in the form of [r, g, b, a] or [r, g, b]. - - """ - # Color.from_string, Color.from_int, ... - if isinstance(color, str) and 6 <= len(color) <= 10: - return Color.from_str(color) - elif issubclass(int, type(color)): - return Color.from_hex(int(color)) - elif isinstance(color, list) and Color.is_valid( - color - ): # If it is a valid color in list form, simply return it - return Color.from_list(color) - raise ValueError("Invalid input") diff --git a/src/czml3/widget.py b/src/czml3/widget.py index 21196d6..5ff865a 100644 --- a/src/czml3/widget.py +++ b/src/czml3/widget.py @@ -1,6 +1,6 @@ from uuid import uuid4 -import attr +from pydantic import BaseModel, Field from .core import Document, Preamble @@ -74,21 +74,19 @@ """ -@attr.s -class CZMLWidget: - document = attr.ib(default=Document([Preamble()])) - cesium_version = attr.ib(default="1.88") - ion_token = attr.ib(default="") - terrain = attr.ib(default=TERRAIN["Ellipsoid"]) - imagery = attr.ib(default=IMAGERY["OSM"]) - - _container_id = attr.ib(factory=uuid4) +class CZMLWidget(BaseModel): + document: Document = Field(default=Document(packets=[Preamble()])) + cesium_version: str = Field(default="1.88") + ion_token: str = Field(default="") + terrain: str = Field(default=TERRAIN["Ellipsoid"]) + imagery: str = Field(default=IMAGERY["OSM"]) + container_id: str = Field(default=str(uuid4)) def build_script(self): return SCRIPT_TPL.format( cesium_version=self.cesium_version, - czml=self.document.dumps(), - container_id=self._container_id, + czml=self.document.to_json(), + container_id=self.container_id, ion_token=self.ion_token, terrain=self.terrain, imagery=self.imagery, @@ -98,7 +96,7 @@ def to_html(self, widget_height="400px"): return CESIUM_TPL.format( cesium_version=self.cesium_version, script=self.build_script(), - container_id=self._container_id, + container_id=self.container_id, widget_height=widget_height, ) diff --git a/tests/simple.czml b/tests/simple.czml index c24e598..095f156 100644 --- a/tests/simple.czml +++ b/tests/simple.czml @@ -7,9 +7,7 @@ "clock":{ "interval":"2012-03-15T10:00:00.000000Z/2012-03-16T10:00:00.000000Z", "currentTime":"2012-03-15T10:00:00.000000Z", - "multiplier":60, - "range":"LOOP_STOP", - "step":"SYSTEM_CLOCK_MULTIPLIER" + "multiplier":60 } }, { diff --git a/tests/test_document.py b/tests/test_document.py index 7931d0a..50bfe21 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -1,5 +1,3 @@ -from io import StringIO - from czml3 import Document, Packet @@ -7,7 +5,7 @@ def test_document_has_expected_packets(): packet0 = Packet(id="id_00") packet1 = Packet(id="id_01") - document = Document([packet0, packet1]) + document = Document(packets=[packet0, packet1]) assert document.packets == [packet0, packet1] @@ -20,29 +18,15 @@ def test_doc_repr(): } ]""" - document = Document([packet]) + document = Document(packets=[packet]) assert str(document) == expected_result def test_doc_dumps(): packet = Packet(id="id_00") - expected_result = """[{"id": "id_00"}]""" + expected_result = """[{"id":"id_00"}]""" - document = Document([packet]) + document = Document(packets=[packet]) assert document.dumps() == expected_result - - -def test_document_dump(): - expected_result = """[{"id": "id_00"}]""" - packet = Packet(id="id_00") - - document = Document([packet]) - - with StringIO() as fp: - document.dump(fp) - fp.seek(0) - result = fp.read() - - assert result == expected_result diff --git a/tests/test_examples.py b/tests/test_examples.py index 402977e..35b945c 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -2,6 +2,7 @@ import os import pytest + from czml3.examples import simple TESTS_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -12,7 +13,7 @@ def test_simple(document, filename): with open(os.path.join(TESTS_DIR, filename)) as fp: expected_result = json.load(fp) - result = json.loads(document.dumps()) + result = json.loads(document.to_json()) for ii, packet in enumerate(result): expected_packet = expected_result[ii] for key in packet: diff --git a/tests/test_packet.py b/tests/test_packet.py index 26dce33..f6f085e 100644 --- a/tests/test_packet.py +++ b/tests/test_packet.py @@ -1,7 +1,8 @@ -from io import StringIO +import ast from uuid import UUID import pytest + from czml3 import CZML_VERSION, Packet, Preamble from czml3.enums import InterpolationAlgorithms, ReferenceFrames from czml3.properties import ( @@ -77,9 +78,7 @@ def test_packet_label(): expected_result = """{ "id": "0", "label": { - "show": true, "font": "20px sans-serif", - "style": "FILL", "fillColor": { "rgbaf": [ 0.2, @@ -103,12 +102,13 @@ def test_packet_label(): id="0", label=Label( font="20px sans-serif", - fillColor=Color.from_list([0.2, 0.3, 0.4]), - outlineColor=Color.from_list([0, 233, 255, 2]), + fillColor=Color(rgbaf=[0.2, 0.3, 0.4, 1.0]), + outlineColor=Color(rgba=[0, 233, 255, 2]), outlineWidth=2.0, ), ) + assert packet == Packet(**ast.literal_eval(expected_result)) assert str(packet) == expected_result @@ -133,44 +133,12 @@ def test_packet_with_delete_has_nothing_else(): def test_packet_dumps(): - expected_result = """{"id": "id_00"}""" + expected_result = """{"id":"id_00"}""" packet = Packet(id="id_00") assert packet.dumps() == expected_result -def test_packet_dump(): - expected_result = """{"id": "id_00"}""" - packet = Packet(id="id_00") - - with StringIO() as fp: - packet.dump(fp) - fp.seek(0) - result = fp.read() - - assert result == expected_result - - -@pytest.mark.xfail -def test_packet_constant_cartesian_position_perfect(): - # Trying to group the cartesian value by sample - # is much more difficult than expected. - # Pull requests welcome - expected_result = """{ - "id": "MyObject", - "position": { - "interpolationAlgorithm": "LINEAR", - "referenceFrame": "FIXED", - "cartesian": [ - 0.0, 0.0, 0.0 - ] - } -}""" - packet = Packet(id="MyObject", position=Position(cartesian=[0.0, 0.0, 0.0])) - - assert str(packet) == expected_result - - def test_packet_constant_cartesian_position(): expected_result = """{ "id": "MyObject", @@ -272,7 +240,6 @@ def test_packet_description(): string = "Description" packet_str = Packet(id="id_00", name="Name", description=string) packet_val = Packet(id="id_00", name="Name", description=StringValue(string=string)) - assert str(packet_str) == str(packet_val) == expected_result @@ -336,7 +303,7 @@ def test_packet_point(): } } }""" - packet = Packet(id="id_00", point=Point(color=Color.from_list([255, 0, 0, 255]))) + packet = Packet(id="id_00", point=Point(color=Color(rgba=[255, 0, 0, 255]))) assert str(packet) == expected_result @@ -376,7 +343,7 @@ def test_packet_polyline(): cartographicDegrees=[-75, 43, 500000, -125, 43, 500000] ), material=PolylineMaterial( - solidColor=SolidColorMaterial.from_list([255, 0, 0, 255]) + solidColor=SolidColorMaterial(color=Color(rgba=[255, 0, 0, 255])) ), ), ) @@ -429,8 +396,8 @@ def test_packet_polyline_outline(): ), material=PolylineOutlineMaterial( polylineOutline=PolylineOutline( - color=Color.from_list([255, 0, 0, 255]), - outlineColor=Color.from_list([255, 0, 0, 255]), + color=Color(rgba=[255, 0, 0, 255]), + outlineColor=Color(rgba=[255, 0, 0, 255]), outlineWidth=2, ) ), @@ -479,7 +446,7 @@ def test_packet_polyline_glow(): ), material=PolylineGlowMaterial( polylineGlow=PolylineGlow( - color=Color.from_list([255, 0, 0, 255]), + color=Color(rgba=[255, 0, 0, 255]), glowPower=0.2, taperPower=0.5, ) @@ -525,7 +492,7 @@ def test_packet_polyline_arrow(): cartographicDegrees=[-75, 43, 500000, -125, 43, 500000] ), material=PolylineArrowMaterial( - polylineArrow=PolylineArrow(color=Color.from_list([255, 0, 0, 255])) + polylineArrow=PolylineArrow(color=Color(rgba=[255, 0, 0, 255])) ), ), ) @@ -568,7 +535,7 @@ def test_packet_polyline_dashed(): cartographicDegrees=[-75, 43, 500000, -125, 43, 500000] ), material=PolylineDashMaterial( - polylineDash=PolylineDash(color=Color.from_list([255, 0, 0, 255])) + polylineDash=PolylineDash(color=Color(rgba=[255, 0, 0, 255])) ), ), ) @@ -584,19 +551,19 @@ def test_packet_polygon(): "cartographicDegrees": [ -115.0, 37.0, - 0, + 0.0, -115.0, 32.0, - 0, + 0.0, -107.0, 33.0, - 0, + 0.0, -102.0, 31.0, - 0, + 0.0, -102.0, 35.0, - 0 + 0.0 ] }, "granularity": 1.0, @@ -637,7 +604,9 @@ def test_packet_polygon(): ] ), granularity=1.0, - material=Material(solidColor=SolidColorMaterial.from_list([255, 0, 0])), + material=Material( + solidColor=SolidColorMaterial(color=Color(rgba=[255, 0, 0])) + ), ), ) diff --git a/tests/test_properties.py b/tests/test_properties.py index d465ea4..96b403a 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,6 +1,8 @@ import datetime as dt import pytest +from pydantic import ValidationError + from czml3.enums import ArcTypes, ClassificationTypes, ShadowModes from czml3.properties import ( ArcType, @@ -52,6 +54,7 @@ Sequence, TimeInterval, UnitQuaternionValue, + format_datetime_like, ) @@ -92,9 +95,9 @@ def test_point(): "pixelSize": 10, "scaleByDistance": { "nearFarScalar": [ - 150, + 150.0, 2.0, - 15000000, + 15000000.0, 0.5 ] }, @@ -178,11 +181,13 @@ def test_material_solid_color(): } } }""" - mat = Material(solidColor=SolidColorMaterial.from_list([200, 100, 30])) + mat = Material(solidColor=SolidColorMaterial(color=Color(rgba=[200, 100, 30]))) assert str(mat) == expected_result - pol_mat = PolylineMaterial(solidColor=SolidColorMaterial.from_list([200, 100, 30])) + pol_mat = PolylineMaterial( + solidColor=SolidColorMaterial(color=Color(rgba=[200, 100, 30])) + ) assert str(pol_mat) == expected_result @@ -298,12 +303,12 @@ def test_outline_material_colors(): def test_positionlist_epoch(): expected_result = """{ + "epoch": "2019-06-11T12:26:58.000000Z", "cartographicDegrees": [ 200, 100, 30 - ], - "epoch": "2019-06-11T12:26:58.000000Z" + ] }""" p = PositionList( epoch=dt.datetime(2019, 6, 11, 12, 26, 58, tzinfo=dt.timezone.utc), @@ -312,32 +317,76 @@ def test_positionlist_epoch(): assert str(p) == expected_result -def test_color_isvalid(): - assert Color.is_valid([255, 204, 0, 55]) - assert Color.is_valid([255, 204, 55]) - assert Color.is_valid(0xFF3223) - assert Color.is_valid(32) - assert Color.is_valid(0xFF322332) - assert Color.is_valid("#FF3223") - assert Color.is_valid("#FF322332") - assert Color.is_valid((255, 204, 55)) - assert Color.is_valid((255, 204, 55, 255)) - assert Color.is_valid((0.127568, 0.566949, 0.550556)) - assert Color.is_valid((0.127568, 0.566949, 0.550556, 1.0)) - - -def test_color_isvalid_false(): - assert Color.is_valid([256, 204, 0, 55]) is False - assert Color.is_valid([-204, 0, 55]) is False - assert Color.is_valid([249.1, 204.3, 55.4]) is False - assert Color.is_valid([255, 204]) is False - assert Color.is_valid([255, 232, 300]) is False - assert Color.is_valid(0xFF3223324) is False - assert Color.is_valid(-3) is False - assert Color.is_valid("totally valid color") is False - assert Color.is_valid("#FF322332432") is False - assert Color.is_valid((255, 204, 55, 255, 42)) is False - assert Color.is_valid((0.127568, 0.566949, 0.550556, 1.0, 3.0)) is False +def test_colors_rgba(): + Color(rgba=[255, 204, 0, 55]) + Color(rgba=[255, 204, 55]) + Color(rgba="0xFF3223") + Color(rgba="0xFF322332") + Color(rgba="#FF3223") + Color(rgba="#FF322332") + Color(rgba=[255, 204, 55]) + Color(rgba=[255, 204, 55, 255]) + Color(rgba=[0.127568, 0.566949, 0.550556]) + Color(rgba=[0.127568, 0.566949, 0.550556, 1.0]) + + +def test_colors_rgbaf(): + Color(rgbaf=[255, 204, 0, 55]) + Color(rgbaf=[255, 204, 55]) + Color(rgbaf="0xFF3223") + Color(rgbaf="0xFF322332") + Color(rgbaf="#FF3223") + Color(rgbaf="#FF322332") + Color(rgbaf=[255, 204, 55]) + Color(rgbaf=[255, 204, 55, 255]) + Color(rgbaf=[0.127568, 0.566949, 0.550556]) + Color(rgbaf=[0.127568, 0.566949, 0.550556, 1.0]) + + +def test_color_invalid_colors_rgba(): + with pytest.raises(TypeError): + Color(rgba=[256, 204, 0, 55]) + with pytest.raises(TypeError): + Color(rgba=[-204, 0, 55]) + with pytest.raises(TypeError): + Color(rgba=[255, 204]) + with pytest.raises(TypeError): + Color(rgba=[255, 232, 300]) + with pytest.raises(TypeError): + Color(rgba="0xFF3223324") + with pytest.raises(TypeError): + Color(rgba=-3) # type: ignore + with pytest.raises(ValidationError): + Color(rgba="totally valid color") + with pytest.raises(TypeError): + Color(rgba="#FF322332432") + with pytest.raises(TypeError): + Color(rgba=[255, 204, 55, 255, 42]) + with pytest.raises(TypeError): + Color(rgba=[0.127568, 0.566949, 0.550556, 1.0, 3.0]) + + +def test_color_invalid_colors_rgbaf(): + with pytest.raises(TypeError): + Color(rgbaf=[256, 204, 0, 55]) + with pytest.raises(TypeError): + Color(rgbaf=[-204, 0, 55]) + with pytest.raises(TypeError): + Color(rgbaf=[255, 204]) + with pytest.raises(TypeError): + Color(rgbaf=[255, 232, 300]) + with pytest.raises(TypeError): + Color(rgbaf="0xFF3223324") + with pytest.raises(TypeError): + Color(rgbaf=-3) # type: ignore + with pytest.raises(ValidationError): + Color(rgbaf="totally valid color") + with pytest.raises(TypeError): + Color(rgbaf="#FF322332432") + with pytest.raises(TypeError): + Color(rgbaf=[255, 204, 55, 255, 42]) + with pytest.raises(TypeError): + Color(rgbaf=[0.127568, 0.566949, 0.550556, 1.0, 3.0]) def test_material_image(): @@ -355,8 +404,7 @@ def test_material_image(): 30, 255 ] - }, - "transparent": false + } } }""" @@ -364,7 +412,7 @@ def test_material_image(): image=ImageMaterial( image=Uri(uri="https://site.com/image.png"), repeat=[2, 2], - color=Color.from_list([200, 100, 30]), + color=Color(rgba=[200, 100, 30]), ) ) assert str(mat) == expected_result @@ -373,7 +421,7 @@ def test_material_image(): image=ImageMaterial( image=Uri(uri="https://site.com/image.png"), repeat=[2, 2], - color=Color.from_list([200, 100, 30]), + color=Color(rgba=[200, 100, 30]), ) ) assert str(pol_mat) == expected_result @@ -405,7 +453,37 @@ def test_material_grid(): }""" pol_mat = GridMaterial( - color=Color.from_list([20, 20, 30]), + color=Color(rgba=[20, 20, 30]), + cellAlpha=1.0, + lineCount=[16, 16], + lineThickness=[2.0, 2.0], + lineOffset=[0.3, 0.4], + ) + assert str(pol_mat) == expected_result + + +def test_nested_delete(): + expected_result = """{ + "color": { + "delete": true + }, + "cellAlpha": 1.0, + "lineCount": [ + 16, + 16 + ], + "lineThickness": [ + 2.0, + 2.0 + ], + "lineOffset": [ + 0.3, + 0.4 + ] +}""" + + pol_mat = GridMaterial( + color=Color(rgba=[20, 20, 30], delete=True), cellAlpha=1.0, lineCount=[16, 16], lineThickness=[2.0, 2.0], @@ -416,7 +494,6 @@ def test_material_grid(): def test_material_stripe(): expected_result = """{ - "orientation": "HORIZONTAL", "evenColor": { "rgba": [ 0, @@ -438,8 +515,8 @@ def test_material_stripe(): }""" pol_mat = StripeMaterial( - evenColor=Color.from_list([0, 0, 0]), - oddColor=Color.from_list([255, 255, 255]), + evenColor=Color(rgba=[0, 0, 0]), + oddColor=Color(rgba=[255, 255, 255]), offset=0.3, repeat=4, ) @@ -468,8 +545,8 @@ def test_material_checkerboard(): }""" pol_mat = CheckerboardMaterial( - evenColor=Color.from_list([0, 0, 0]), - oddColor=Color.from_list([255, 255, 255]), + evenColor=Color(rgba=[0, 0, 0]), + oddColor=Color(rgba=[255, 255, 255]), repeat=4, ) assert str(pol_mat) == expected_result @@ -482,7 +559,7 @@ def test_position_has_delete(): def test_position_no_values_raises_error(): - with pytest.raises(ValueError) as exc: + with pytest.raises(TypeError) as exc: Position() assert ( @@ -502,7 +579,9 @@ def test_position_with_delete_has_nothing_else(): def test_position_has_given_epoch(): - expected_epoch = dt.datetime(2019, 6, 11, 12, 26, 58, tzinfo=dt.timezone.utc) + expected_epoch = format_datetime_like( + dt.datetime(2019, 6, 11, 12, 26, 58, tzinfo=dt.timezone.utc) + ) pos = Position(epoch=expected_epoch, cartesian=[]) @@ -510,7 +589,9 @@ def test_position_has_given_epoch(): def test_positionlist_has_given_epoch(): - expected_epoch = dt.datetime(2019, 6, 11, 12, 26, 58, tzinfo=dt.timezone.utc) + expected_epoch = format_datetime_like( + dt.datetime(2019, 6, 11, 12, 26, 58, tzinfo=dt.timezone.utc) + ) pos = PositionList(epoch=expected_epoch, cartesian=[]) @@ -544,18 +625,21 @@ def test_position_cartographic_degrees(): def test_position_reference(): expected_result = """{ - "reference": "satellite" + "reference": "#satellite" }""" - pos = Position(reference="satellite") + pos = Position(reference="#satellite") assert str(pos) == expected_result def test_viewfrom_reference(): expected_result = """{ - "reference": "satellite" + "cartesian": [ + 1.0 + ], + "reference": "#satellite" }""" - v = ViewFrom(reference="satellite") + v = ViewFrom(reference="#satellite", cartesian=[1.0]) assert str(v) == expected_result @@ -574,16 +658,14 @@ def test_viewfrom_cartesian(): def test_viewfrom_has_delete(): - v = ViewFrom(delete=True, cartesian=[]) + v = ViewFrom(delete=True, cartesian=[14.0, 12.0]) assert v.delete def test_viewfrom_no_values_raises_error(): - with pytest.raises(ValueError) as exc: - ViewFrom() - - assert "One of cartesian or reference must be given" in exc.exconly() + with pytest.raises(ValidationError) as _: + ViewFrom() # type: ignore def test_single_interval_value(): @@ -617,7 +699,7 @@ def test_multiple_interval_value(): end1 = dt.datetime(2019, 1, 3, tzinfo=dt.timezone.utc) prop = Sequence( - [ + values=[ IntervalValue(start=start0, end=end0, value=True), IntervalValue(start=start1, end=end1, value=False), ] @@ -643,7 +725,7 @@ def test_multiple_interval_decimal_value(): end1 = dt.datetime(2019, 1, 3, 1, 2, 3, 456789, tzinfo=dt.timezone.utc) prop = Sequence( - [ + values=[ IntervalValue(start=start0, end=end0, value=True), IntervalValue(start=start1, end=end1, value=False), ] @@ -680,7 +762,7 @@ def test_model(): def test_bad_uri_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: Uri(uri="a") assert "uri must be a URL or a data URI" in excinfo.exconly() @@ -762,32 +844,6 @@ def test_ellipsoid_parameters(): assert str(ell) == expected_result -def test_color_rgbaf_from_tuple(): - expected_result = """{ - "rgbaf": [ - 0.127568, - 0.566949, - 0.550556, - 1.0 - ] -}""" - tc = Color.from_tuple((0.127568, 0.566949, 0.550556, 1.0)) - assert str(tc) == expected_result - - -def test_color_rgba_from_tuple(): - expected_result = """{ - "rgba": [ - 100, - 200, - 255, - 255 - ] -}""" - tc = Color.from_tuple((100, 200, 255)) - assert str(tc) == expected_result - - def test_polygon_with_hole(): expected_result = """{ "positions": { @@ -922,9 +978,6 @@ def test_polygon_interval_with_position(): def test_label_offset(): expected_result = """{ - "show": true, - "style": "FILL", - "outlineWidth": 1.0, "pixelOffset": { "cartesian2": [ 5, @@ -939,8 +992,8 @@ def test_label_offset(): def test_tileset(): expected_result = """{ - "show": true, - "uri": "../SampleData/Cesium3DTiles/Batched/BatchedColors/tileset.json" + "uri": "../SampleData/Cesium3DTiles/Batched/BatchedColors/tileset.json", + "show": true }""" tileset = Tileset( show=True, uri="../SampleData/Cesium3DTiles/Batched/BatchedColors/tileset.json" diff --git a/tests/test_rectangle_image.py b/tests/test_rectangle_image.py index ea35ab7..a56aaa1 100644 --- a/tests/test_rectangle_image.py +++ b/tests/test_rectangle_image.py @@ -3,6 +3,7 @@ import tempfile import pytest + from czml3 import Document, Packet, Preamble from czml3.properties import ImageMaterial, Material, Rectangle, RectangleCoordinates @@ -18,13 +19,10 @@ def image(): def test_rectangle_coordinates_invalid_if_nothing_given(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RectangleCoordinates() - assert ( - "One of cartesian, cartographicDegrees or cartographicRadians must be given" - in excinfo.exconly() - ) + assert "One of wsen or wsenDegrees must be given" in excinfo.exconly() def test_packet_rectangles(image): @@ -70,12 +68,10 @@ def test_packet_rectangles(image): def test_make_czml_png_rectangle_file(image): - wsen = [20, 40, 21, 41] - rectangle_packet = Packet( id="id_00", rectangle=Rectangle( - coordinates=RectangleCoordinates(wsenDegrees=wsen), + coordinates=RectangleCoordinates(wsenDegrees=[20, 40, 21, 41]), fill=True, material=Material( image=ImageMaterial( @@ -88,7 +84,7 @@ def test_make_czml_png_rectangle_file(image): ) with tempfile.NamedTemporaryFile(mode="w", suffix=".czml") as out_file: - out_file.write(str(Document([Preamble(), rectangle_packet]))) + out_file.write(str(Document(packets=[Preamble(), rectangle_packet]))) exists = os.path.isfile(out_file.name) # TODO: Should we be testing something else? diff --git a/tests/test_types.py b/tests/test_types.py index 51de622..f4f1cad 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -2,7 +2,8 @@ import astropy.time import pytest -from czml3.base import BaseCZMLObject +from pydantic import ValidationError + from czml3.types import ( Cartesian3Value, CartographicDegreesListValue, @@ -20,11 +21,10 @@ UnitQuaternionValue, format_datetime_like, ) -from dateutil.tz import tzoffset def test_invalid_near_far_scalar_value(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: NearFarScalarValue(values=[0, 3.2, 1, 4, 2, 1]) assert "Input values must have either 4 or N * 5 values, " in excinfo.exconly() @@ -59,7 +59,7 @@ def test_cartographic_radian_list(): def test_invalid_cartograpic_radian_list(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: CartographicRadiansListValue(values=[1]) assert ( "Invalid values. Input values should be arrays of size 3 * N" @@ -78,7 +78,7 @@ def test_cartograpic_degree_list(): def test_invalid_cartograpic_degree_list(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: CartographicDegreesListValue(values=[15, 25, 50, 30]) assert ( "Invalid values. Input values should be arrays of size 3 * N" @@ -88,7 +88,7 @@ def test_invalid_cartograpic_degree_list(): @pytest.mark.parametrize("values", [[2, 2], [5, 5, 5, 5, 5]]) def test_bad_cartesian_raises_error(values): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: Cartesian3Value(values=values) assert "Input values must have either 3 or N * 4 values" in excinfo.exconly() @@ -102,7 +102,7 @@ def test_reference_value(): def test_invalid_reference_value(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: ReferenceValue(string="id") assert ( @@ -126,66 +126,54 @@ def test_font_property_value(): def test_bad_rgba_size_values_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RgbaValue(values=[0, 0, 255]) assert "Input values must have either 4 or N * 5 values, " in excinfo.exconly() def test_bad_rgba_4_values_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RgbaValue(values=[256, 0, 0, 255]) assert "Color values must be integers in the range 0-255." in excinfo.exconly() def test_bad_rgba_5_color_values_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RgbaValue(values=[0, 0.1, 0.3, 0.3, 255]) assert "Color values must be integers in the range 0-255." in excinfo.exconly() def test_bad_rgbaf_size_values_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RgbafValue(values=[0, 0, 0.1]) assert "Input values must have either 4 or N * 5 values, " in excinfo.exconly() def test_bad_rgbaf_4_values_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RgbafValue(values=[0.3, 0, 0, 1.4]) assert "Color values must be floats in the range 0-1." in excinfo.exconly() def test_bad_rgbaf_5_color_values_raises_error(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(TypeError) as excinfo: RgbafValue(values=[0, 0.1, 0.3, 0.3, 255]) assert "Color values must be floats in the range 0-1." in excinfo.exconly() def test_default_time_interval(): - expected_result = '"0000-00-00T00:00:00Z/9999-12-31T24:00:00Z"' + expected_result = '"0001-01-01T00:00:00Z/9999-12-31T23:59:59Z"' time_interval = TimeInterval() assert str(time_interval) == expected_result -def test_custom_time_interval(): - tz = tzoffset("UTC+02", dt.timedelta(hours=2)) - start = dt.datetime(2019, 1, 1, 12, 0, tzinfo=dt.timezone.utc) - end = dt.datetime(2019, 9, 2, 23, 59, 59, tzinfo=tz) - - expected_result = '"2019-01-01T12:00:00.000000Z/2019-09-02T21:59:59.000000Z"' - - time_interval = TimeInterval(start=start, end=end) - - assert str(time_interval) == expected_result - - def test_bad_time_raises_error(): with pytest.raises(ValueError): format_datetime_like("2019/01/01") @@ -204,19 +192,6 @@ def test_interval_value(): }""" ) - # value is something that has a "to_json" method - class CustomValue(BaseCZMLObject): - def to_json(self): - return {"foo": "bar"} - - assert ( - str(IntervalValue(start=start, end=end, value=CustomValue())) - == """{ - "interval": "2019-01-01T12:00:00.000000Z/2019-09-02T21:59:59.000000Z", - "foo": "bar" -}""" - ) - assert ( str( IntervalValue( @@ -258,16 +233,11 @@ def test_epoch_value(): }""" ) - with pytest.raises(expected_exception=ValueError): + with pytest.raises(ValueError): str(EpochValue(value="test")) - with pytest.raises( - expected_exception=ValueError, - match="Epoch must be a string or a datetime object.", - ): - EpochValue(value=1) - +@pytest.mark.xfail(reason="NumberValue class requires further explanaition") def test_numbers_value(): expected_result = """{ "number": [ @@ -288,20 +258,13 @@ def test_numbers_value(): assert str(numbers) == expected_result - with pytest.raises( - expected_exception=ValueError, match="Values must be integers or floats." - ): - NumberValue(values="test") + with pytest.raises(ValidationError): + NumberValue(values="test") # type: ignore - with pytest.raises( - expected_exception=ValueError, match="Values must be integers or floats." - ): - NumberValue(values=[1, "test"]) + with pytest.raises(ValidationError): + NumberValue(values=[1, "test"]) # type: ignore - with pytest.raises( - expected_exception=ValueError, - match="Values must be a list of number pairs signifying the time and representative value.", - ): + with pytest.raises(ValidationError): NumberValue(values=[1, 2, 3, 4, 5]) diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index f7bdd26..0000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from czml3.properties import Color -from czml3.types import RgbafValue, RgbaValue -from czml3.utils import get_color, get_color_list - - -def test_get_color_list_of_colors_rgba(): - expected_color = Color( - rgba=RgbaValue( - values=[ - "0000-00-00T00:00:00.000000Z", - 255, - 204, - 0, - 255, - "9999-12-31T24:00:00.000000Z", - 255, - 204, - 0, - 255, - ] - ) - ) - assert ( - get_color_list( - ["0000-00-00T00:00:00.000000Z", "9999-12-31T24:00:00.000000Z"], - [[1.0, 0.8, 0.0, 1.0], 0xFFCC00FF], - ) - == expected_color - ) - assert ( - get_color_list( - ["0000-00-00T00:00:00.000000Z", "9999-12-31T24:00:00.000000Z"], - ["#ffcc00ff", 0xFFCC00], - ) - == expected_color - ) - - -def test_get_color_list_of_colors_rgbaf(): - expected_color = Color( - rgbaf=RgbafValue( - values=[ - "0000-00-00T00:00:00.000000Z", - 1.0, - 0.8, - 0.0, - 1.0, - "9999-12-31T24:00:00.000000Z", - 1.0, - 0.8, - 0.0, - 1.0, - ] - ) - ) - assert ( - get_color_list( - ["0000-00-00T00:00:00.000000Z", "9999-12-31T24:00:00.000000Z"], - [[1.0, 0.8, 0.0, 1.0], 0xFFCC00], - rgbaf=True, - ) - == expected_color - ) - assert ( - get_color_list( - ["0000-00-00T00:00:00.000000Z", "9999-12-31T24:00:00.000000Z"], - [[255, 204, 0], 0xFFCC00FF], - rgbaf=True, - ) - == expected_color - ) - - -def test_get_color_list_of_colors_invalid(): - with pytest.raises(ValueError): - get_color_list( - ["0000-00-00T00:00:00.000000Z", "9999-12-31T24:00:00.000000Z"], - [[300, 204, 0], -0xFFCC00FF], - rgbaf=True, - ) - - -def test_get_color_rgba(): - expected_color = Color(rgba=RgbaValue(values=[255, 204, 0, 255])) - - assert get_color("#ffcc00") == expected_color - assert get_color(0xFFCC00) == expected_color - assert get_color("#ffcc00ff") == expected_color - assert get_color(0xFFCC00FF) == expected_color - assert get_color([255, 204, 0]) == expected_color - assert get_color([255, 204, 0, 255]) == expected_color - - -def test_get_color_rgbaf(): - expected_color = Color(rgbaf=RgbafValue(values=[1.0, 0.8, 0.0, 1.0])) - - # TODO: Simplify after https://github.com/poliastro/czml3/issues/36 - assert get_color([1.0, 0.8, 0.0]) == expected_color - assert get_color([1.0, 0.8, 0.0, 1.0]) == expected_color - - -@pytest.mark.parametrize("input", ["a", [0, 0, 0, 0, -300], [0.3, 0.3, 0.1, 1.0, 1.0]]) -def test_get_color_invalid_input_raises_error(input): - with pytest.raises(ValueError): - get_color(input) diff --git a/tests/test_widget.py b/tests/test_widget.py index b3009e9..d358cf5 100644 --- a/tests/test_widget.py +++ b/tests/test_widget.py @@ -1,4 +1,5 @@ import pytest + from czml3.widget import CZMLWidget From 50b81aa65927260772189ca6476fea9adff528a3 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 22:44:39 +0200 Subject: [PATCH 02/20] Remove empty Field() calls --- src/czml3/core.py | 2 +- src/czml3/properties.py | 26 +++++++++++++------------- src/czml3/types.py | 30 +++++++++++++++--------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/czml3/core.py b/src/czml3/core.py index c57a705..b42ac7f 100644 --- a/src/czml3/core.py +++ b/src/czml3/core.py @@ -81,7 +81,7 @@ class Packet(BaseCZMLObject): class Document(BaseCZMLObject): """A CZML document, consisting on a list of packets.""" - packets: list[Packet | Preamble] = Field() + packets: list[Packet | Preamble] @model_serializer def custom_serializer(self): diff --git a/src/czml3/properties.py b/src/czml3/properties.py index 3a5bd04..5c09439 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -273,7 +273,7 @@ class ViewFrom(BaseCZMLObject, Interpolatable, Deletable): ViewFrom can optionally vary over time.""" - cartesian: None | Cartesian3Value | list[float] | list[int] = Field() + cartesian: None | Cartesian3Value | list[float] | list[int] reference: None | str = Field(default=None) @field_validator("reference") @@ -290,7 +290,7 @@ class Billboard(BaseCZMLObject, HasAlignment): A billboard is sometimes called a marker. """ - image: str | Uri = Field() + image: str | Uri show: None | bool = Field(default=None) scale: None | float | int = Field(default=None) pixelOffset: None | list[float] | list[int] = Field(default=None) @@ -301,7 +301,7 @@ class Billboard(BaseCZMLObject, HasAlignment): class EllipsoidRadii(BaseCZMLObject, Interpolatable, Deletable): """The radii of an ellipsoid.""" - cartesian: None | Cartesian3Value | list[float] | list[int] = Field() + cartesian: None | Cartesian3Value | list[float] | list[int] reference: None | str = Field(default=None) @field_validator("reference") @@ -317,7 +317,7 @@ class Corridor(BaseCZMLObject): positions: PositionList | list[int] | list[float] show: None | bool = Field(default=None) - width: float | int = Field() + width: float | int height: None | float | int = Field(default=None) heightReference: None | HeightReference = Field(default=None) extrudedHeight: None | float | int = Field(default=None) @@ -338,10 +338,10 @@ class Corridor(BaseCZMLObject): class Cylinder(BaseCZMLObject): """A cylinder, which is a special cone defined by length, top and bottom radius.""" - length: float | int = Field() + length: float | int show: None | bool = Field(default=None) - topRadius: float | int = Field() - bottomRadius: float | int = Field() + topRadius: float | int + bottomRadius: float | int heightReference: None | HeightReference = Field(default=None) fill: None | bool = Field(default=None) material: None | Material | str = Field(default=None) @@ -357,8 +357,8 @@ class Cylinder(BaseCZMLObject): class Ellipse(BaseCZMLObject): """An ellipse, which is a close curve, on or above Earth's surface.""" - semiMajorAxis: float | int = Field() - semiMinorAxis: float | int = Field() + semiMajorAxis: float | int + semiMinorAxis: float | int show: None | bool = Field(default=None) height: None | float | int = Field(default=None) heightReference: None | HeightReference = Field(default=None) @@ -382,7 +382,7 @@ class Ellipse(BaseCZMLObject): class Polygon(BaseCZMLObject): """A polygon, which is a closed figure on the surface of the Earth.""" - positions: Position | PositionList | list[int] | list[float] = Field() + positions: Position | PositionList | list[int] | list[float] show: None | bool = Field(default=None) arcType: None | ArcType = Field(default=None) granularity: None | float | int = Field(default=None) @@ -403,7 +403,7 @@ class Polygon(BaseCZMLObject): class Polyline(BaseCZMLObject): """A polyline, which is a line in the scene composed of multiple segments.""" - positions: PositionList = Field() + positions: PositionList show: None | bool = Field(default=None) arcType: None | ArcType = Field(default=None) width: None | float | int = Field(default=None) @@ -727,7 +727,7 @@ class Wall(BaseCZMLObject): """ show: None | bool = Field(default=None) - positions: PositionList = Field() + positions: PositionList minimumHeights: None | list[float] | list[int] = Field(default=None) maximumHeights: None | list[float] | list[int] = Field(default=None) granularity: None | float | int = Field(default=None) @@ -802,7 +802,7 @@ class Model(BaseCZMLObject): """A 3D model.""" show: None | bool = Field(default=None) - gltf: str = Field() + gltf: str scale: None | float | int = Field(default=None) minimumPixelSize: None | float | int = Field(default=None) maximumScale: None | float | int = Field(default=None) diff --git a/src/czml3/types.py b/src/czml3/types.py index d6c83bf..4024d00 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -119,7 +119,7 @@ def format_datetime_like(dt_object): class FontValue(BaseCZMLObject): """A font, specified using the same syntax as the CSS "font" property.""" - font: str = Field() + font: str @model_serializer def custom_serializer(self): @@ -135,7 +135,7 @@ class RgbafValue(BaseCZMLObject): """ - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -175,7 +175,7 @@ class RgbaValue(BaseCZMLObject): """ - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -212,7 +212,7 @@ class ReferenceValue(BaseCZMLObject): """ - string: str = Field() + string: str @field_validator("string") @classmethod @@ -368,7 +368,7 @@ class StringValue(BaseCZMLObject): The string can optionally vary with time. """ - string: str = Field() + string: str @model_serializer def custom_serializer(self) -> str: @@ -379,7 +379,7 @@ class CartographicRadiansListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in radians and Height is in meters.""" - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -399,7 +399,7 @@ class CartographicDegreesListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in degrees and Height is in meters.""" - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -422,7 +422,7 @@ class DistanceDisplayConditionValue(BaseCZMLObject): where Time is an ISO 8601 date and time string or seconds since epoch. """ - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -446,7 +446,7 @@ class NearFarScalarValue(BaseCZMLObject): FarDistance, FarValue, ...], where Time is an ISO 8601 date and time string or seconds since epoch. """ - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -484,8 +484,8 @@ def custom_serializer(self) -> str: class IntervalValue(BaseCZMLObject): """Value over some interval.""" - start: str | dt.datetime = Field() - end: str | dt.datetime = Field() + start: str | dt.datetime + end: str | dt.datetime value: Any = Field(default=None) @model_serializer @@ -513,7 +513,7 @@ def custom_serializer(self) -> dict[str, Any]: class Sequence(BaseCZMLObject): """Sequence, list, array of objects.""" - values: list[Any] = Field() + values: list[Any] @model_serializer def custom_serializer(self) -> list[Any]: @@ -530,7 +530,7 @@ class UnitQuaternionValue(BaseCZMLObject): """ - values: list[float] | list[int] = Field() + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -549,7 +549,7 @@ def custom_serializer(self): class EpochValue(BaseCZMLObject): """A value representing a time epoch.""" - value: str | dt.datetime = Field() + value: str | dt.datetime @model_serializer def custom_serializer(self): @@ -559,7 +559,7 @@ def custom_serializer(self): class NumberValue(BaseCZMLObject): """A single number, or a list of number pairs signifying the time and representative value.""" - values: int | float | list[float] | list[int] = Field() + values: int | float | list[float] | list[int] @model_serializer def custom_serializer(self): From 0275ecc69cfe480c87e382b1b927bedba53065ec Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 22:44:55 +0200 Subject: [PATCH 03/20] Bug fixes --- src/czml3/properties.py | 1 - src/czml3/types.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/czml3/properties.py b/src/czml3/properties.py index 5c09439..a57b1d8 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -246,7 +246,6 @@ def checks(self): self.cartographicDegrees, self.cartographicRadians, self.cartesianVelocity, - self.reference, ) ) != 1 diff --git a/src/czml3/types.py b/src/czml3/types.py index 4024d00..26f38a5 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -89,7 +89,7 @@ def get_color(color): def check_reference(r): - if re.search(r"^.+#.+$", r) is not None: + if re.search(r"^.+#.+$", r) is None: raise TypeError( "Invalid reference string format. Input must be of the form id#property" ) @@ -203,7 +203,7 @@ def _check_values(self) -> Self: @model_serializer def custom_serializer(self): - return self + return self.values class ReferenceValue(BaseCZMLObject): From e3bb8d6542e2048a9f297d20dde9f589c9b2f8f9 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 22:45:11 +0200 Subject: [PATCH 04/20] Add more tests --- src/czml3/properties.py | 2 +- tests/test_properties.py | 183 ++++++++++++++++++++++++++++++++++++++- tests/test_types.py | 90 +++++++++++++++++++ 3 files changed, 270 insertions(+), 5 deletions(-) diff --git a/src/czml3/properties.py b/src/czml3/properties.py index a57b1d8..abaf5c3 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -582,9 +582,9 @@ class Rectangle(BaseCZMLObject, Interpolatable, Deletable): class RectangleCoordinates(BaseCZMLObject, Interpolatable, Deletable): """A set of coordinates describing a cartographic rectangle on the surface of the ellipsoid.""" - reference: None | str = Field(default=None) wsen: None | list[float] | list[int] = Field(default=None) wsenDegrees: None | list[float] | list[int] = Field(default=None) + reference: None | str = Field(default=None) @model_validator(mode="after") def checks(self): diff --git a/tests/test_properties.py b/tests/test_properties.py index 96b403a..a85e734 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -11,11 +11,17 @@ CheckerboardMaterial, ClassificationType, Color, + ColorBlendMode, + ColorBlendModes, + CornerType, + CornerTypes, DistanceDisplayCondition, Ellipsoid, EllipsoidRadii, EyeOffset, GridMaterial, + HeightReference, + HeightReferences, ImageMaterial, Label, Material, @@ -37,6 +43,7 @@ Position, PositionList, PositionListOfLists, + RectangleCoordinates, ShadowMode, SolidColorMaterial, StripeMaterial, @@ -320,6 +327,9 @@ def test_positionlist_epoch(): def test_colors_rgba(): Color(rgba=[255, 204, 0, 55]) Color(rgba=[255, 204, 55]) + Color(rgba=[0.5, 0.6, 0.2]) + Color(rgba="0xFF0000") + Color(rgba="0xFFFFFFFF") Color(rgba="0xFF3223") Color(rgba="0xFF322332") Color(rgba="#FF3223") @@ -625,9 +635,12 @@ def test_position_cartographic_degrees(): def test_position_reference(): expected_result = """{ - "reference": "#satellite" + "cartesian": [ + 0 + ], + "reference": "this#satellite" }""" - pos = Position(reference="#satellite") + pos = Position(cartesian=[0], reference="this#satellite") assert str(pos) == expected_result @@ -637,9 +650,9 @@ def test_viewfrom_reference(): "cartesian": [ 1.0 ], - "reference": "#satellite" + "reference": "this#satellite" }""" - v = ViewFrom(reference="#satellite", cartesian=[1.0]) + v = ViewFrom(reference="this#satellite", cartesian=[1.0]) assert str(v) == expected_result @@ -999,3 +1012,165 @@ def test_tileset(): show=True, uri="../SampleData/Cesium3DTiles/Batched/BatchedColors/tileset.json" ) assert str(tileset) == expected_result + + +def test_check_classes_with_references(): + assert ( + str(ViewFrom(cartesian=[0, 0], reference="this#that")) + == """{ + "cartesian": [ + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str(EllipsoidRadii(cartesian=[0, 0], reference="this#that")) + == """{ + "cartesian": [ + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str(ArcType(arcType=ArcTypes.GEODESIC, reference="this#that")) + == """{ + "arcType": "GEODESIC", + "reference": "this#that" +}""" + ) + assert ( + str(Position(cartesian=[0, 0], reference="this#that")) + == """{ + "cartesian": [ + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str(Orientation(unitQuaternion=[0, 0, 0, 0], reference="this#that")) + == """{ + "unitQuaternion": [ + 0, + 0, + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str(NearFarScalar(nearFarScalar=[0, 0], reference="this#that")) + == """{ + "nearFarScalar": [ + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str(CornerType(cornerType=CornerTypes.BEVELED, reference="this#that")) + == """{ + "cornerType": "BEVELED", + "reference": "this#that" +}""" + ) + assert ( + str( + ColorBlendMode( + colorBlendMode=ColorBlendModes.HIGHLIGHT, reference="this#that" + ) + ) + == """{ + "colorBlendMode": "HIGHLIGHT", + "reference": "this#that" +}""" + ) + assert ( + str( + HeightReference( + heightReference=HeightReferences.NONE, reference="this#that" + ) + ) + == """{ + "heightReference": "NONE", + "reference": "this#that" +}""" + ) + assert ( + str(EyeOffset(cartesian=[0, 0], reference="this#that")) + == """{ + "cartesian": [ + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str(RectangleCoordinates(wsen=[0, 0], reference="this#that")) + == """{ + "wsen": [ + 0, + 0 + ], + "reference": "this#that" +}""" + ) + assert ( + str( + BoxDimensions( + cartesian=Cartesian3Value(values=[0, 0, 1]), reference="this#that" + ) + ) + == """{ + "cartesian": [ + 0, + 0, + 1 + ], + "reference": "this#that" +}""" + ) + assert ( + str( + DistanceDisplayCondition( + distanceDisplayCondition=DistanceDisplayConditionValue( + values=[0, 1, 2] + ), + reference="this#that", + ) + ) + == """{ + "distanceDisplayCondition": [ + 0, + 1, + 2 + ], + "reference": "this#that" +}""" + ) + assert ( + str( + ClassificationType( + classificationType=ClassificationTypes.BOTH, reference="this#that" + ) + ) + == """{ + "classificationType": "BOTH", + "reference": "this#that" +}""" + ) + assert ( + str(ShadowMode(shadowMode=ShadowModes.CAST_ONLY, reference="this#that")) + == """{ + "shadowMode": "CAST_ONLY", + "reference": "this#that" +}""" + ) diff --git a/tests/test_types.py b/tests/test_types.py index f4f1cad..9afed3a 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -7,7 +7,9 @@ from czml3.types import ( Cartesian3Value, CartographicDegreesListValue, + CartographicDegreesValue, CartographicRadiansListValue, + CartographicRadiansValue, DistanceDisplayConditionValue, EpochValue, FontValue, @@ -300,3 +302,91 @@ def test_quaternion_value(): result = UnitQuaternionValue(values=[0, 0, 0, 1]) assert str(result) == expected_result + + +def test_cartographic_radians_value(): + result = CartographicRadiansValue(values=[0, 0, 0, 1]) + assert ( + str(result) + == """[ + 0, + 0, + 0, + 1 +]""" + ) + result = CartographicRadiansValue(values=[0, 0, 1]) + assert ( + str(result) + == """[ + 0, + 0, + 1 +]""" + ) + + +def test_cartographic_degrees_value(): + result = CartographicDegreesValue(values=[0, 0, 0, 1]) + assert ( + str(result) + == """[ + 0, + 0, + 0, + 1 +]""" + ) + result = CartographicRadiansValue(values=[0, 0, 1]) + assert ( + str(result) + == """[ + 0, + 0, + 1 +]""" + ) + + +def test_rgba_value(): + assert ( + str(RgbaValue(values=[30, 30, 30, 30])) + == """[ + 30, + 30, + 30, + 30 +]""" + ) + assert ( + str(RgbaValue(values=[30, 30, 30, 30, 1])) + == """[ + 30, + 30, + 30, + 30, + 1 +]""" + ) + + +def test_rgbaf_value(): + assert ( + str(RgbafValue(values=[0.5, 0.5, 0.5, 0.5])) + == """[ + 0.5, + 0.5, + 0.5, + 0.5 +]""" + ) + assert ( + str(RgbafValue(values=[0.5, 0.5, 0.5, 0.5, 1])) + == """[ + 0.5, + 0.5, + 0.5, + 0.5, + 1 +]""" + ) From a175ec52b25b9b321dcd3cc579a1e4f5d35e5eb5 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 22:47:50 +0200 Subject: [PATCH 05/20] Fix test_rgbaf_value() --- tests/test_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_types.py b/tests/test_types.py index 9afed3a..61cd67b 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -387,6 +387,6 @@ def test_rgbaf_value(): 0.5, 0.5, 0.5, - 1 + 1.0 ]""" ) From 5202c13222bfce315b8d04a0f54030c16ae0d59e Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 22:53:35 +0200 Subject: [PATCH 06/20] Fix mypy issues --- tests/test_properties.py | 12 ++++++++---- tests/test_types.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_properties.py b/tests/test_properties.py index a85e734..115b791 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -3,7 +3,14 @@ import pytest from pydantic import ValidationError -from czml3.enums import ArcTypes, ClassificationTypes, ShadowModes +from czml3.enums import ( + ArcTypes, + ClassificationTypes, + ColorBlendModes, + CornerTypes, + HeightReferences, + ShadowModes, +) from czml3.properties import ( ArcType, Box, @@ -12,16 +19,13 @@ ClassificationType, Color, ColorBlendMode, - ColorBlendModes, CornerType, - CornerTypes, DistanceDisplayCondition, Ellipsoid, EllipsoidRadii, EyeOffset, GridMaterial, HeightReference, - HeightReferences, ImageMaterial, Label, Material, diff --git a/tests/test_types.py b/tests/test_types.py index 61cd67b..9c61b77 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -337,7 +337,7 @@ def test_cartographic_degrees_value(): 1 ]""" ) - result = CartographicRadiansValue(values=[0, 0, 1]) + result = CartographicDegreesValue(values=[0, 0, 1]) assert ( str(result) == """[ From 88580f33f56333a02057657e3f263ba3657e79ac Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 23:10:14 +0200 Subject: [PATCH 07/20] Add tests --- tests/test_types.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/test_types.py b/tests/test_types.py index 9c61b77..db3d3dc 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -5,6 +5,7 @@ from pydantic import ValidationError from czml3.types import ( + Cartesian2Value, Cartesian3Value, CartographicDegreesListValue, CartographicDegreesValue, @@ -32,6 +33,13 @@ def test_invalid_near_far_scalar_value(): assert "Input values must have either 4 or N * 5 values, " in excinfo.exconly() +def test_distance_display_condition_is_invalid(): + with pytest.raises(TypeError): + DistanceDisplayConditionValue( + values=[0, 150, 15000000, 300, 10000, 15000000, 600] + ) + + def test_distance_display_condition(): expected_result = """[ 0, @@ -89,11 +97,21 @@ def test_invalid_cartograpic_degree_list(): @pytest.mark.parametrize("values", [[2, 2], [5, 5, 5, 5, 5]]) -def test_bad_cartesian_raises_error(values): +def test_bad_cartesian3_raises_error(values): with pytest.raises(TypeError) as excinfo: Cartesian3Value(values=values) assert "Input values must have either 3 or N * 4 values" in excinfo.exconly() + assert str(Cartesian3Value()) == "{}" + + +@pytest.mark.parametrize("values", [[2, 2, 2, 2, 2], [5, 5, 5, 5, 5]]) +def test_bad_cartesian2_raises_error(values): + with pytest.raises(TypeError) as excinfo: + Cartesian2Value(values=values) + + assert "Input values must have either 2 or N * 3 values" in excinfo.exconly() + assert str(Cartesian2Value()) == "{}" def test_reference_value(): @@ -291,6 +309,11 @@ def test_astropy_time_format(): assert result == expected_result +def test_quaternion_value_is_invalid(): + with pytest.raises(TypeError): + UnitQuaternionValue(values=[0, 0, 0, 1, 0]) + + def test_quaternion_value(): expected_result = """[ 0, @@ -324,6 +347,10 @@ def test_cartographic_radians_value(): 1 ]""" ) + result = CartographicRadiansValue() + assert str(result) == """[]""" + with pytest.raises(TypeError): + CartographicRadiansValue(values=[0, 0, 1, 1, 1, 1]) def test_cartographic_degrees_value(): @@ -346,6 +373,10 @@ def test_cartographic_degrees_value(): 1 ]""" ) + result = CartographicDegreesValue() + assert str(result) == """[]""" + with pytest.raises(TypeError): + CartographicDegreesValue(values=[0, 0, 1, 1, 1, 1]) def test_rgba_value(): From 6aeaa1f13b9232b8836b0d23f772f51646c8aa93 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 23:11:52 +0200 Subject: [PATCH 08/20] Fix test_bad_cartesian3_raises_error() --- tests/test_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_types.py b/tests/test_types.py index db3d3dc..9c1e7ca 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -102,7 +102,7 @@ def test_bad_cartesian3_raises_error(values): Cartesian3Value(values=values) assert "Input values must have either 3 or N * 4 values" in excinfo.exconly() - assert str(Cartesian3Value()) == "{}" + assert str(Cartesian3Value()) == "[]" @pytest.mark.parametrize("values", [[2, 2, 2, 2, 2], [5, 5, 5, 5, 5]]) From 73abbae74d5ede29a28820a41964c9ddd1554066 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 23:27:14 +0200 Subject: [PATCH 09/20] Fix check_reference() for None input type --- src/czml3/types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/czml3/types.py b/src/czml3/types.py index 26f38a5..dd30f1a 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -89,7 +89,9 @@ def get_color(color): def check_reference(r): - if re.search(r"^.+#.+$", r) is None: + if r is None: + return + elif re.search(r"^.+#.+$", r) is None: raise TypeError( "Invalid reference string format. Input must be of the form id#property" ) From 2e27beaf96598d79cf4bec7bece55b1042cf12d4 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Mon, 25 Nov 2024 23:27:24 +0200 Subject: [PATCH 10/20] Add tests --- tests/test_properties.py | 9 +++++++++ tests/test_types.py | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/test_properties.py b/tests/test_properties.py index 115b791..43385eb 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1178,3 +1178,12 @@ def test_check_classes_with_references(): "reference": "this#that" }""" ) + + +def test_rectangle_coordinates_delete(): + assert ( + str(RectangleCoordinates(wsen=[0, 0], reference="this#that", delete=True)) + == """{ + "delete": true +}""" + ) diff --git a/tests/test_types.py b/tests/test_types.py index 9c1e7ca..44b2537 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -22,6 +22,7 @@ RgbaValue, TimeInterval, UnitQuaternionValue, + check_reference, format_datetime_like, ) @@ -421,3 +422,13 @@ def test_rgbaf_value(): 1.0 ]""" ) + + +def test_check_reference(): + with pytest.raises(TypeError): + check_reference("thisthat") + assert check_reference("this#that") is None + + +def test_format_datetime_like(): + assert format_datetime_like(None) is None From 879e020049a8cbef2d9257434ebb59777dec873c Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Tue, 26 Nov 2024 13:57:24 +0200 Subject: [PATCH 11/20] Support Pythons 3.7 to 3.10 --- pyproject.toml | 10 +- src/czml3/base.py | 14 +- src/czml3/common.py | 9 +- src/czml3/core.py | 62 ++-- src/czml3/enums.py | 33 ++- src/czml3/properties.py | 628 +++++++++++++++++++++------------------- src/czml3/types.py | 54 ++-- 7 files changed, 428 insertions(+), 382 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9360367..e700a14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,9 @@ [tool.ruff.lint] -ignore = ["E203", "E266", "E501"] +ignore = [ + "E501", + "UP007", # support Pythons <3.11 + "UP006", # support Pythons <3.11 +] select = [ "E", # pycodestyle "F", # Pyflakes @@ -103,7 +107,9 @@ classifiers = [ dependencies = [ "pydantic>=2.10.1", "python-dateutil>=2.7,<3", - "w3lib" + "w3lib", + "typing-extensions>=4.12.0", + "StrEnum>=0.4.0", ] dynamic = ["version"] diff --git a/src/czml3/base.py b/src/czml3/base.py index a797cb8..88ed282 100644 --- a/src/czml3/base.py +++ b/src/czml3/base.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Dict from pydantic import BaseModel, model_validator @@ -8,13 +8,13 @@ class BaseCZMLObject(BaseModel): @model_validator(mode="before") @classmethod - def check_model_before(cls, data: dict[str, Any]) -> Any: + def check_model_before(cls, data: Dict[str, Any]) -> Any: if data is not None and "delete" in data and data["delete"]: - return ( - {"delete": True} - | ({"id": data["id"]} if "id" in data else {}) - | {k: None for k in data if k not in NON_DELETE_PROPERTIES} - ) + return { + "delete": True, + "id": data.get("id"), + **{k: None for k in data if k not in NON_DELETE_PROPERTIES}, + } return data def __str__(self) -> str: diff --git a/src/czml3/common.py b/src/czml3/common.py index 0a3c6c9..ea82a94 100644 --- a/src/czml3/common.py +++ b/src/czml3/common.py @@ -1,4 +1,5 @@ import datetime as dt +from typing import Union from pydantic import BaseModel, field_validator @@ -9,7 +10,7 @@ class Deletable(BaseModel): """A property whose value may be deleted.""" - delete: None | bool = None + delete: Union[None, bool] = None class Interpolatable(BaseModel): @@ -18,9 +19,9 @@ class Interpolatable(BaseModel): The interpolation happens over provided time-tagged samples. """ - epoch: None | str | dt.datetime = None - interpolationAlgorithm: None | InterpolationAlgorithms = None - interpolationDegree: None | int = None + epoch: Union[None, str, dt.datetime] = None + interpolationAlgorithm: Union[None, InterpolationAlgorithms] = None + interpolationDegree: Union[None, int] = None @field_validator("epoch") @classmethod diff --git a/src/czml3/core.py b/src/czml3/core.py index b42ac7f..86eaf6b 100644 --- a/src/czml3/core.py +++ b/src/czml3/core.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, List, Union from uuid import uuid4 from pydantic import Field, model_serializer @@ -35,11 +35,11 @@ class Preamble(BaseCZMLObject): """The preamble packet.""" - id: str = Field(default="document") - version: str = Field(default=CZML_VERSION) - name: None | str = Field(default=None) - description: None | str = Field(default=None) - clock: None | Clock | IntervalValue = Field(default=None) + id: Union[str] = Field(default="document") + version: Union[str] = Field(default=CZML_VERSION) + name: Union[None, str] = Field(default=None) + description: Union[None, str] = Field(default=None) + clock: Union[None, Clock, IntervalValue] = Field(default=None) class Packet(BaseCZMLObject): @@ -50,38 +50,38 @@ class Packet(BaseCZMLObject): """ id: str = Field(default=str(uuid4())) - delete: None | bool = Field(default=None) - name: None | str = Field(default=None) - parent: None | str = Field(default=None) - description: None | str | StringValue = Field(default=None) - availability: None | TimeInterval | list[TimeInterval] | Sequence = Field( + delete: Union[None, bool] = Field(default=None) + name: Union[None, str] = Field(default=None) + parent: Union[None, str] = Field(default=None) + description: Union[None, str, StringValue] = Field(default=None) + availability: Union[None, TimeInterval, List[TimeInterval], Sequence] = Field( default=None ) - properties: None | Any = Field(default=None) - position: None | Position = Field(default=None) - orientation: None | Orientation = Field(default=None) - viewFrom: None | ViewFrom = Field(default=None) - billboard: None | Billboard = Field(default=None) - box: None | Box = Field(default=None) - corridor: None | Corridor = Field(default=None) - cylinder: None | Cylinder = Field(default=None) - ellipse: None | Ellipse = Field(default=None) - ellipsoid: None | Ellipsoid = Field(default=None) - label: None | Label = Field(default=None) - model: None | Model = Field(default=None) - path: None | Path = Field(default=None) - point: None | Point = Field(default=None) - polygon: None | Polygon = Field(default=None) - polyline: None | Polyline = Field(default=None) - rectangle: None | Rectangle = Field(default=None) - tileset: None | Tileset = Field(default=None) - wall: None | Wall = Field(default=None) + properties: Union[None, Any] = Field(default=None) + position: Union[None, Position] = Field(default=None) + orientation: Union[None, Orientation] = Field(default=None) + viewFrom: Union[None, ViewFrom] = Field(default=None) + billboard: Union[None, Billboard] = Field(default=None) + box: Union[None, Box] = Field(default=None) + corridor: Union[None, Corridor] = Field(default=None) + cylinder: Union[None, Cylinder] = Field(default=None) + ellipse: Union[None, Ellipse] = Field(default=None) + ellipsoid: Union[None, Ellipsoid] = Field(default=None) + label: Union[None, Label] = Field(default=None) + model: Union[None, Model] = Field(default=None) + path: Union[None, Path] = Field(default=None) + point: Union[None, Point] = Field(default=None) + polygon: Union[None, Polygon] = Field(default=None) + polyline: Union[None, Polyline] = Field(default=None) + rectangle: Union[None, Rectangle] = Field(default=None) + tileset: Union[None, Tileset] = Field(default=None) + wall: Union[None, Wall] = Field(default=None) class Document(BaseCZMLObject): """A CZML document, consisting on a list of packets.""" - packets: list[Packet | Preamble] + packets: List[Union[Packet, Preamble]] @model_serializer def custom_serializer(self): diff --git a/src/czml3/enums.py b/src/czml3/enums.py index 997a826..f1233f8 100644 --- a/src/czml3/enums.py +++ b/src/czml3/enums.py @@ -1,17 +1,22 @@ -from enum import StrEnum, auto -from typing import Any - - -class OCaseStrEnum(StrEnum): - """ - StrEnum where enum.auto() returns the original member name, not lower-cased name. - """ - - @staticmethod - def _generate_next_value_( - name: str, start: int, count: int, last_values: list[Any] - ) -> str: - return name +import sys +from enum import auto +from typing import Any, List + +if sys.version_info[1] >= 11: + from enum import StrEnum + + class OCaseStrEnum(StrEnum): + """ + StrEnum where enum.auto() returns the original member name, not lower-cased name. + """ + + @staticmethod + def _generate_next_value_( + name: str, start: int, count: int, last_values: List[Any] + ) -> str: + return name +else: + from strenum import StrEnum as OCaseStrEnum class InterpolationAlgorithms(OCaseStrEnum): diff --git a/src/czml3/properties.py b/src/czml3/properties.py index abaf5c3..678f8b8 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -1,7 +1,7 @@ from __future__ import annotations import datetime as dt -from typing import Any +from typing import Any, List, Union from pydantic import ( BaseModel, @@ -48,19 +48,19 @@ class HasAlignment(BaseModel): """A property that can be horizontally or vertically aligned.""" - horizontalOrigin: None | HorizontalOrigins = Field(default=None) - verticalOrigin: None | VerticalOrigins = Field(default=None) + horizontalOrigin: Union[None, HorizontalOrigins] = Field(default=None) + verticalOrigin: Union[None, VerticalOrigins] = Field(default=None) class Material(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - solidColor: None | Color | SolidColorMaterial | str = Field(default=None) - image: None | ImageMaterial | str | Uri = Field(default=None) - grid: None | GridMaterial = Field(default=None) - stripe: None | StripeMaterial = Field(default=None) - checkerboard: None | CheckerboardMaterial = Field(default=None) - polylineOutline: None | PolylineMaterial = Field( + solidColor: Union[None, Color, SolidColorMaterial, str] = Field(default=None) + image: Union[None, ImageMaterial, str, Uri] = Field(default=None) + grid: Union[None, GridMaterial] = Field(default=None) + stripe: Union[None, StripeMaterial] = Field(default=None) + checkerboard: Union[None, CheckerboardMaterial] = Field(default=None) + polylineOutline: Union[None, PolylineMaterial] = Field( default=None ) # NOTE: Not present in documentation @@ -68,117 +68,117 @@ class Material(BaseCZMLObject): class PolylineOutline(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - color: None | Color | str = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | int | float = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, int, float] = Field(default=None) class PolylineOutlineMaterial(BaseCZMLObject): """A definition of the material wrapper for a polyline outline.""" - polylineOutline: None | PolylineOutline = Field(default=None) + polylineOutline: Union[None, PolylineOutline] = Field(default=None) class PolylineGlow(BaseCZMLObject): """A definition of how a glowing polyline appears.""" - color: None | Color | str = Field(default=None) - glowPower: None | float | int = Field(default=None) - taperPower: None | float | int = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + glowPower: Union[None, float, int] = Field(default=None) + taperPower: Union[None, float, int] = Field(default=None) class PolylineGlowMaterial(BaseCZMLObject): """A material that fills the surface of a line with a glowing color.""" - polylineGlow: None | PolylineGlow = Field(default=None) + polylineGlow: Union[None, PolylineGlow] = Field(default=None) class PolylineArrow(BaseCZMLObject): """A definition of how a polyline arrow appears.""" - color: None | Color | str = Field(default=None) + color: Union[None, Color, str] = Field(default=None) class PolylineArrowMaterial(BaseCZMLObject): """A material that fills the surface of a line with an arrow.""" - polylineArrow: None | PolylineArrow = Field(default=None) + polylineArrow: Union[None, PolylineArrow] = Field(default=None) class PolylineDash(BaseCZMLObject): """A definition of how a polyline should be dashed with two colors.""" - color: None | Color | str = Field(default=None) - gapColor: None | Color | str = Field(default=None) - dashLength: None | float | int = Field(default=None) - dashPattern: None | int = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + gapColor: Union[None, Color, str] = Field(default=None) + dashLength: Union[None, float, int] = Field(default=None) + dashPattern: Union[None, int] = Field(default=None) class PolylineDashMaterial(BaseCZMLObject): """A material that provides a how a polyline should be dashed.""" - polylineDash: None | PolylineDash = Field(default=None) + polylineDash: Union[None, PolylineDash] = Field(default=None) class PolylineMaterial(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - solidColor: None | SolidColorMaterial | str = Field(default=None) - image: None | ImageMaterial | str | Uri = Field(default=None) - grid: None | GridMaterial = Field(default=None) - stripe: None | StripeMaterial = Field(default=None) - checkerboard: None | CheckerboardMaterial = Field(default=None) - polylineDash: None | PolylineDashMaterial = Field(default=None) + solidColor: Union[None, SolidColorMaterial, str] = Field(default=None) + image: Union[None, ImageMaterial, str, Uri] = Field(default=None) + grid: Union[None, GridMaterial] = Field(default=None) + stripe: Union[None, StripeMaterial] = Field(default=None) + checkerboard: Union[None, CheckerboardMaterial] = Field(default=None) + polylineDash: Union[None, PolylineDashMaterial] = Field(default=None) class SolidColorMaterial(BaseCZMLObject): """A material that fills the surface with a solid color.""" - color: None | Color | str = Field(default=None) + color: Union[None, Color, str] = Field(default=None) class GridMaterial(BaseCZMLObject): """A material that fills the surface with a two-dimensional grid.""" - color: None | Color | str = Field(default=None) - cellAlpha: None | float | int = Field(default=None) - lineCount: None | list[int] = Field(default=None) - lineThickness: None | list[float] | list[int] = Field(default=None) - lineOffset: None | list[float] | list[int] = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + cellAlpha: Union[None, float, int] = Field(default=None) + lineCount: Union[None, List[int]] = Field(default=None) + lineThickness: Union[None, List[float], List[int]] = Field(default=None) + lineOffset: Union[None, List[float], List[int]] = Field(default=None) class StripeMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - orientation: None | int = Field(default=None) - evenColor: None | Color | str = Field(default=None) - oddColor: None | Color | str = Field(default=None) - offset: None | float | int = Field(default=None) - repeat: None | float | int = Field(default=None) + orientation: Union[None, int] = Field(default=None) + evenColor: Union[None, Color, str] = Field(default=None) + oddColor: Union[None, Color, str] = Field(default=None) + offset: Union[None, float, int] = Field(default=None) + repeat: Union[None, float, int] = Field(default=None) class CheckerboardMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - evenColor: None | Color | str = Field(default=None) - oddColor: None | Color | str = Field(default=None) - repeat: None | int = Field(default=None) + evenColor: Union[None, Color, str] = Field(default=None) + oddColor: Union[None, Color, str] = Field(default=None) + repeat: Union[None, int] = Field(default=None) class ImageMaterial(BaseCZMLObject): """A material that fills the surface with an image.""" - image: None | ImageMaterial | str | Uri = Field(default=None) - repeat: None | list[int] = Field(default=None) - color: None | Color | str = Field(default=None) - transparent: None | bool = Field(default=None) + image: Union[None, ImageMaterial, str, Uri] = Field(default=None) + repeat: Union[None, List[int]] = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + transparent: Union[None, bool] = Field(default=None) class Color(BaseCZMLObject, Interpolatable, Deletable): """A color. The color can optionally vary over time.""" - rgba: None | RgbaValue | str | list[float] | list[int] = Field(default=None) - rgbaf: None | RgbafValue | str | list[float] | list[int] = Field(default=None) + rgba: Union[None, RgbaValue, str, List[float], List[int]] = Field(default=None) + rgbaf: Union[None, RgbafValue, str, List[float], List[int]] = Field(default=None) @field_validator("rgba", "rgbaf") @classmethod @@ -225,14 +225,16 @@ def is_valid(cls, color): class Position(BaseCZMLObject, Interpolatable, Deletable): """Defines a position. The position can optionally vary over time.""" - referenceFrame: None | str = Field(default=None) - cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) - cartographicRadians: None | list[float] | list[int] = Field(default=None) - cartographicDegrees: None | list[float] | list[int] = Field(default=None) - cartesianVelocity: None | list[float] | list[int] = Field(default=None) - reference: None | str = Field(default=None) - interval: None | TimeInterval = Field(default=None) - epoch: None | str | dt.datetime = Field(default=None) + referenceFrame: Union[None, str] = Field(default=None) + cartesian: Union[None, Cartesian3Value, List[float], List[int]] = Field( + default=None + ) + cartographicRadians: Union[None, List[float], List[int]] = Field(default=None) + cartographicDegrees: Union[None, List[float], List[int]] = Field(default=None) + cartesianVelocity: Union[None, List[float], List[int]] = Field(default=None) + reference: Union[None, str] = Field(default=None) + interval: Union[None, TimeInterval] = Field(default=None) + epoch: Union[None, str, dt.datetime] = Field(default=None) @model_validator(mode="after") def checks(self): @@ -272,8 +274,8 @@ class ViewFrom(BaseCZMLObject, Interpolatable, Deletable): ViewFrom can optionally vary over time.""" - cartesian: None | Cartesian3Value | list[float] | list[int] - reference: None | str = Field(default=None) + cartesian: Union[None, Cartesian3Value, List[float], List[int]] + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -289,19 +291,19 @@ class Billboard(BaseCZMLObject, HasAlignment): A billboard is sometimes called a marker. """ - image: str | Uri - show: None | bool = Field(default=None) - scale: None | float | int = Field(default=None) - pixelOffset: None | list[float] | list[int] = Field(default=None) - eyeOffset: None | list[float] | list[int] = Field(default=None) - color: None | Color | str = Field(default=None) + image: Union[str, Uri] + show: Union[None, bool] = Field(default=None) + scale: Union[None, float, int] = Field(default=None) + pixelOffset: Union[None, List[float], List[int]] = Field(default=None) + eyeOffset: Union[None, List[float], List[int]] = Field(default=None) + color: Union[None, Color, str] = Field(default=None) class EllipsoidRadii(BaseCZMLObject, Interpolatable, Deletable): """The radii of an ellipsoid.""" - cartesian: None | Cartesian3Value | list[float] | list[int] - reference: None | str = Field(default=None) + cartesian: Union[None, Cartesian3Value, List[float], List[int]] + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -314,130 +316,140 @@ class Corridor(BaseCZMLObject): """A corridor , which is a shape defined by a centerline and width that conforms to the curvature of the body shape. It can can optionally be extruded into a volume.""" - positions: PositionList | list[int] | list[float] - show: None | bool = Field(default=None) - width: float | int - height: None | float | int = Field(default=None) - heightReference: None | HeightReference = Field(default=None) - extrudedHeight: None | float | int = Field(default=None) - extrudedHeightReference: None | HeightReference = Field(default=None) - cornerType: None | CornerType = Field(default=None) - granularity: None | float | int = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) - outline: None | Color | str = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | int | float = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) - classificationType: None | ClassificationType = Field(default=None) - zIndex: None | int = Field(default=None) + positions: Union[PositionList, List[int], List[float]] + show: Union[None, bool] = Field(default=None) + width: Union[float, int] + height: Union[None, float, int] = Field(default=None) + heightReference: Union[None, HeightReference] = Field(default=None) + extrudedHeight: Union[None, float, int] = Field(default=None) + extrudedHeightReference: Union[None, HeightReference] = Field(default=None) + cornerType: Union[None, CornerType] = Field(default=None) + granularity: Union[None, float, int] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + outline: Union[None, Color, str] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, int, float] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) + classificationType: Union[None, ClassificationType] = Field(default=None) + zIndex: Union[None, int] = Field(default=None) class Cylinder(BaseCZMLObject): """A cylinder, which is a special cone defined by length, top and bottom radius.""" - length: float | int - show: None | bool = Field(default=None) - topRadius: float | int - bottomRadius: float | int - heightReference: None | HeightReference = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) - outline: None | bool = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - numberOfVerticalLines: None | int = Field(default=None) - slices: None | int = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + length: Union[float, int] + show: Union[None, bool] = Field(default=None) + topRadius: Union[float, int] + bottomRadius: Union[float, int] + heightReference: Union[None, HeightReference] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + outline: Union[None, bool] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + numberOfVerticalLines: Union[None, int] = Field(default=None) + slices: Union[None, int] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) class Ellipse(BaseCZMLObject): """An ellipse, which is a close curve, on or above Earth's surface.""" - semiMajorAxis: float | int - semiMinorAxis: float | int - show: None | bool = Field(default=None) - height: None | float | int = Field(default=None) - heightReference: None | HeightReference = Field(default=None) - extrudedHeight: None | float | int = Field(default=None) - extrudedHeightReference: None | HeightReference = Field(default=None) - rotation: None | float | int = Field(default=None) - stRotation: None | float | int = Field(default=None) - granularity: None | float | int = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) - outline: None | bool = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - numberOfVerticalLines: None | int = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) - classificationType: None | ClassificationType = Field(default=None) - zIndex: None | int = Field(default=None) + semiMajorAxis: Union[float, int] + semiMinorAxis: Union[float, int] + show: Union[None, bool] = Field(default=None) + height: Union[None, float, int] = Field(default=None) + heightReference: Union[None, HeightReference] = Field(default=None) + extrudedHeight: Union[None, float, int] = Field(default=None) + extrudedHeightReference: Union[None, HeightReference] = Field(default=None) + rotation: Union[None, float, int] = Field(default=None) + stRotation: Union[None, float, int] = Field(default=None) + granularity: Union[None, float, int] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + outline: Union[None, bool] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + numberOfVerticalLines: Union[None, int] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) + classificationType: Union[None, ClassificationType] = Field(default=None) + zIndex: Union[None, int] = Field(default=None) class Polygon(BaseCZMLObject): """A polygon, which is a closed figure on the surface of the Earth.""" - positions: Position | PositionList | list[int] | list[float] - show: None | bool = Field(default=None) - arcType: None | ArcType = Field(default=None) - granularity: None | float | int = Field(default=None) - material: None | Material | str = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) - classificationType: None | ClassificationType = Field(default=None) - zIndex: None | int = Field(default=None) - holes: None | PositionList | PositionListOfLists | list[int] | list[float] = Field( + positions: Union[Position, PositionList, List[int], List[float]] + show: Union[None, bool] = Field(default=None) + arcType: Union[None, ArcType] = Field(default=None) + granularity: Union[None, float, int] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( default=None + ) + classificationType: Union[None, ClassificationType] = Field(default=None) + zIndex: Union[None, int] = Field(default=None) + holes: Union[None, PositionList, PositionListOfLists, List[int], List[float]] = ( + Field(default=None) ) # NOTE: not in documentation - outlineColor: None | Color | str = Field(default=None) - outline: None | bool = Field(default=None) - extrudedHeight: None | float | int = Field(default=None) - perPositionHeight: None | bool = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outline: Union[None, bool] = Field(default=None) + extrudedHeight: Union[None, float, int] = Field(default=None) + perPositionHeight: Union[None, bool] = Field(default=None) class Polyline(BaseCZMLObject): """A polyline, which is a line in the scene composed of multiple segments.""" positions: PositionList - show: None | bool = Field(default=None) - arcType: None | ArcType = Field(default=None) - width: None | float | int = Field(default=None) - granularity: None | float | int = Field(default=None) - material: ( - None - | PolylineMaterial - | PolylineDashMaterial - | PolylineArrowMaterial - | PolylineGlowMaterial - | PolylineOutlineMaterial - | str - ) = Field(default=None) - followSurface: None | bool = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - depthFailMaterial: ( - None - | PolylineMaterial - | PolylineDashMaterial - | PolylineArrowMaterial - | PolylineGlowMaterial - | PolylineOutlineMaterial - | str - ) = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) - clampToGround: None | bool = Field(default=None) - classificationType: None | ClassificationType = Field(default=None) - zIndex: None | int = Field(default=None) + show: Union[None, bool] = Field(default=None) + arcType: Union[None, ArcType] = Field(default=None) + width: Union[None, float, int] = Field(default=None) + granularity: Union[None, float, int] = Field(default=None) + material: Union[ + None, + PolylineMaterial, + PolylineDashMaterial, + PolylineArrowMaterial, + PolylineGlowMaterial, + PolylineOutlineMaterial, + str, + ] = Field(default=None) + followSurface: Union[None, bool] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + depthFailMaterial: Union[ + None, + PolylineMaterial, + PolylineDashMaterial, + PolylineArrowMaterial, + PolylineGlowMaterial, + PolylineOutlineMaterial, + str, + ] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) + clampToGround: Union[None, bool] = Field(default=None) + classificationType: Union[None, ClassificationType] = Field(default=None) + zIndex: Union[None, int] = Field(default=None) class ArcType(BaseCZMLObject, Deletable): """The type of an arc.""" - arcType: None | ArcTypes | str = Field(default=None) - reference: None | str = Field(default=None) + arcType: Union[None, ArcTypes, str] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -449,8 +461,8 @@ def check(cls, r): class ShadowMode(BaseCZMLObject, Deletable): """Whether or not an object casts or receives shadows from each light source when shadows are enabled.""" - shadowMode: None | ShadowModes = Field(default=None) - reference: None | str = Field(default=None) + shadowMode: Union[None, ShadowModes] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -462,8 +474,8 @@ def check(cls, r): class ClassificationType(BaseCZMLObject, Deletable): """Whether a classification affects terrain, 3D Tiles, or both.""" - classificationType: None | ClassificationTypes = Field(default=None) - reference: None | str = Field(default=None) + classificationType: Union[None, ClassificationTypes] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -475,8 +487,10 @@ def check(cls, r): class DistanceDisplayCondition(BaseCZMLObject, Interpolatable, Deletable): """Indicates the visibility of an object based on the distance to the camera.""" - distanceDisplayCondition: None | DistanceDisplayConditionValue = Field(default=None) - reference: None | str = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayConditionValue] = Field( + default=None + ) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -488,31 +502,33 @@ def check(cls, r): class PositionListOfLists(BaseCZMLObject, Deletable): """A list of positions.""" - referenceFrame: None | str | list[str] = Field(default=None) - cartesian: None | Cartesian3Value = Field(default=None) - cartographicRadians: ( - None | list[float] | list[int] | list[list[float]] | list[list[int]] - ) = Field(default=None) - cartographicDegrees: ( - None | list[float] | list[int] | list[list[float]] | list[list[int]] - ) = Field(default=None) - references: None | str | list[str] = Field(default=None) + referenceFrame: Union[None, str, List[str]] = Field(default=None) + cartesian: Union[None, Cartesian3Value] = Field(default=None) + cartographicRadians: Union[ + None, List[float], List[int], List[List[float]], List[List[int]] + ] = Field(default=None) + cartographicDegrees: Union[ + None, List[float], List[int], List[List[float]], List[List[int]] + ] = Field(default=None) + references: Union[None, str, List[str]] = Field(default=None) class PositionList(BaseCZMLObject, Interpolatable, Deletable): """A list of positions.""" - referenceFrame: None | str | list[str] = Field(default=None) - cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) - cartographicRadians: ( - None | list[float] | list[int] | CartographicRadiansListValue - ) = Field(default=None) - cartographicDegrees: ( - None | list[float] | list[int] | CartographicDegreesListValue - ) = Field(default=None) - references: None | str | list[str] = Field(default=None) - interval: None | TimeInterval = Field(default=None) - epoch: None | str | dt.datetime = Field(default=None) # note: not documented + referenceFrame: Union[None, str, List[str]] = Field(default=None) + cartesian: Union[None, Cartesian3Value, List[float], List[int]] = Field( + default=None + ) + cartographicRadians: Union[ + None, List[float], List[int], CartographicRadiansListValue + ] = Field(default=None) + cartographicDegrees: Union[ + None, List[float], List[int], CartographicDegreesListValue + ] = Field(default=None) + references: Union[None, str, List[str]] = Field(default=None) + interval: Union[None, TimeInterval] = Field(default=None) + epoch: Union[None, str, dt.datetime] = Field(default=None) # note: not documented @field_validator("epoch") @classmethod @@ -524,43 +540,45 @@ class Ellipsoid(BaseCZMLObject): """A closed quadric surface that is a three-dimensional analogue of an ellipse.""" radii: EllipsoidRadii - innerRadii: None | EllipsoidRadii = Field(default=None) - minimumClock: None | float | int = Field(default=None) - maximumClock: None | float | int = Field(default=None) - minimumCone: None | float | int = Field(default=None) - maximumCone: None | float | int = Field(default=None) - show: None | bool = Field(default=None) - heightReference: None | HeightReference = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) - outline: None | bool = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - stackPartitions: None | int = Field(default=None) - slicePartitions: None | int = Field(default=None) - subdivisions: None | int = Field(default=None) + innerRadii: Union[None, EllipsoidRadii] = Field(default=None) + minimumClock: Union[None, float, int] = Field(default=None) + maximumClock: Union[None, float, int] = Field(default=None) + minimumCone: Union[None, float, int] = Field(default=None) + maximumCone: Union[None, float, int] = Field(default=None) + show: Union[None, bool] = Field(default=None) + heightReference: Union[None, HeightReference] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + outline: Union[None, bool] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + stackPartitions: Union[None, int] = Field(default=None) + slicePartitions: Union[None, int] = Field(default=None) + subdivisions: Union[None, int] = Field(default=None) class Box(BaseCZMLObject): """A box, which is a closed rectangular cuboid.""" - show: None | bool = Field(default=None) - dimensions: None | BoxDimensions = Field(default=None) - heightReference: None | HeightReference = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) - outline: None | bool = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + show: Union[None, bool] = Field(default=None) + dimensions: Union[None, BoxDimensions] = Field(default=None) + heightReference: Union[None, HeightReference] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + outline: Union[None, bool] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) class BoxDimensions(BaseCZMLObject, Interpolatable): """The width, depth, and height of a box.""" - cartesian: None | Cartesian3Value = Field(default=None) - reference: None | str = Field(default=None) + cartesian: Union[None, Cartesian3Value] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -574,17 +592,17 @@ class Rectangle(BaseCZMLObject, Interpolatable, Deletable): can be placed on the surface or at altitude and can optionally be extruded into a volume. """ - coordinates: None | RectangleCoordinates = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) + coordinates: Union[None, RectangleCoordinates] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) class RectangleCoordinates(BaseCZMLObject, Interpolatable, Deletable): """A set of coordinates describing a cartographic rectangle on the surface of the ellipsoid.""" - wsen: None | list[float] | list[int] = Field(default=None) - wsenDegrees: None | list[float] | list[int] = Field(default=None) - reference: None | str = Field(default=None) + wsen: Union[None, List[float], List[int]] = Field(default=None) + wsenDegrees: Union[None, List[float], List[int]] = Field(default=None) + reference: Union[None, str] = Field(default=None) @model_validator(mode="after") def checks(self): @@ -610,8 +628,10 @@ class EyeOffset(BaseCZMLObject, Deletable): """ - cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) - reference: None | str = Field(default=None) + cartesian: Union[None, Cartesian3Value, List[float], List[int]] = Field( + default=None + ) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -623,8 +643,8 @@ def check(cls, r): class HeightReference(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - heightReference: None | HeightReferences = Field(default=None) - reference: None | str = Field(default=None) + heightReference: Union[None, HeightReferences] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -636,8 +656,8 @@ def check(cls, r): class ColorBlendMode(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - colorBlendMode: None | ColorBlendModes = Field(default=None) - reference: None | str = Field(default=None) + colorBlendMode: Union[None, ColorBlendModes] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -649,8 +669,8 @@ def check(cls, r): class CornerType(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - cornerType: None | CornerTypes = Field(default=None) - reference: None | str = Field(default=None) + cornerType: Union[None, CornerTypes] = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -666,10 +686,10 @@ class Clock(BaseCZMLObject): """ - currentTime: None | str | dt.datetime = Field(default=None) - multiplier: None | float | int = Field(default=None) - range: None | ClockRanges = Field(default=None) - step: None | ClockSteps = Field(default=None) + currentTime: Union[None, str, dt.datetime] = Field(default=None) + multiplier: Union[None, float, int] = Field(default=None) + range: Union[None, ClockRanges] = Field(default=None) + step: Union[None, ClockSteps] = Field(default=None) @field_validator("currentTime") @classmethod @@ -688,36 +708,40 @@ class Path(BaseCZMLObject): """ - show: None | bool | Sequence = Field(default=None) - leadTime: None | float | int = Field(default=None) - trailTime: None | float | int = Field(default=None) - width: None | float | int = Field(default=None) - resolution: None | float | int = Field(default=None) - material: None | Material | str = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + show: Union[None, bool, Sequence] = Field(default=None) + leadTime: Union[None, float, int] = Field(default=None) + trailTime: Union[None, float, int] = Field(default=None) + width: Union[None, float, int] = Field(default=None) + resolution: Union[None, float, int] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) class Point(BaseCZMLObject): """A point, or viewport-aligned circle.""" - show: None | bool = Field(default=None) - pixelSize: None | float | int = Field(default=None) - heightReference: None | HeightReference = Field(default=None) - color: None | Color | str = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - scaleByDistance: None | NearFarScalar = Field(default=None) - translucencyByDistance: None | NearFarScalar = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) - disableDepthTestDistance: None | float | int = Field(default=None) + show: Union[None, bool] = Field(default=None) + pixelSize: Union[None, float, int] = Field(default=None) + heightReference: Union[None, HeightReference] = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + scaleByDistance: Union[None, NearFarScalar] = Field(default=None) + translucencyByDistance: Union[None, NearFarScalar] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) + disableDepthTestDistance: Union[None, float, int] = Field(default=None) class Tileset(BaseCZMLObject): """A 3D Tiles tileset.""" - uri: str | Uri - show: None | bool = Field(default=None) - maximumScreenSpaceError: None | float | int = Field(default=None) + uri: Union[str, Uri] + show: Union[None, bool] = Field(default=None) + maximumScreenSpaceError: Union[None, float, int] = Field(default=None) class Wall(BaseCZMLObject): @@ -725,18 +749,20 @@ class Wall(BaseCZMLObject): It conforms to the curvature of the globe and can be placed along the surface or at altitude. """ - show: None | bool = Field(default=None) + show: Union[None, bool] = Field(default=None) positions: PositionList - minimumHeights: None | list[float] | list[int] = Field(default=None) - maximumHeights: None | list[float] | list[int] = Field(default=None) - granularity: None | float | int = Field(default=None) - fill: None | bool = Field(default=None) - material: None | Material | str = Field(default=None) - outline: None | bool = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + minimumHeights: Union[None, List[float], List[int]] = Field(default=None) + maximumHeights: Union[None, List[float], List[int]] = Field(default=None) + granularity: Union[None, float, int] = Field(default=None) + fill: Union[None, bool] = Field(default=None) + material: Union[None, Material, str] = Field(default=None) + outline: Union[None, bool] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): @@ -748,10 +774,10 @@ class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): less than the near distance or greater than the far distance, respectively. """ - nearFarScalar: None | list[float] | list[int] | NearFarScalarValue = Field( + nearFarScalar: Union[None, List[float], List[int], NearFarScalarValue] = Field( default=None ) - reference: None | str = Field(default=None) + reference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -763,17 +789,17 @@ def check(cls, r): class Label(BaseCZMLObject, HasAlignment): """A string of text.""" - show: None | bool = Field(default=None) - text: None | str = Field(default=None) - font: None | str = Field(default=None) - style: None | LabelStyles = Field(default=None) - scale: None | float | int = Field(default=None) - showBackground: None | bool = Field(default=None) - backgroundColor: None | Color | str = Field(default=None) - fillColor: None | Color | str = Field(default=None) - outlineColor: None | Color | str = Field(default=None) - outlineWidth: None | float | int = Field(default=None) - pixelOffset: None | float | int | Cartesian2Value = Field(default=None) + show: Union[None, bool] = Field(default=None) + text: Union[None, str] = Field(default=None) + font: Union[None, str] = Field(default=None) + style: Union[None, LabelStyles] = Field(default=None) + scale: Union[None, float, int] = Field(default=None) + showBackground: Union[None, bool] = Field(default=None) + backgroundColor: Union[None, Color, str] = Field(default=None) + fillColor: Union[None, Color, str] = Field(default=None) + outlineColor: Union[None, Color, str] = Field(default=None) + outlineWidth: Union[None, float, int] = Field(default=None) + pixelOffset: Union[None, float, int, Cartesian2Value] = Field(default=None) class Orientation(BaseCZMLObject, Interpolatable, Deletable): @@ -784,11 +810,11 @@ class Orientation(BaseCZMLObject, Interpolatable, Deletable): """ - unitQuaternion: None | list[float] | list[int] | UnitQuaternionValue = Field( + unitQuaternion: Union[None, List[float], List[int], UnitQuaternionValue] = Field( default=None ) - reference: None | str = Field(default=None) - velocityReference: None | str = Field(default=None) + reference: Union[None, str] = Field(default=None) + velocityReference: Union[None, str] = Field(default=None) @field_validator("reference") @classmethod @@ -800,23 +826,25 @@ def check(cls, r): class Model(BaseCZMLObject): """A 3D model.""" - show: None | bool = Field(default=None) + show: Union[None, bool] = Field(default=None) gltf: str - scale: None | float | int = Field(default=None) - minimumPixelSize: None | float | int = Field(default=None) - maximumScale: None | float | int = Field(default=None) - incrementallyLoadTextures: None | bool = Field(default=None) - runAnimations: None | bool = Field(default=None) - shadows: None | ShadowMode = Field(default=None) - heightReference: None | HeightReference = Field(default=None) - silhouetteColor: None | Color | str = Field(default=None) - silhouetteSize: None | Color | str = Field(default=None) - color: None | Color | str = Field(default=None) - colorBlendMode: None | ColorBlendMode = Field(default=None) - colorBlendAmount: None | float | int = Field(default=None) - distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) - nodeTransformations: None | Any = Field(default=None) - articulations: None | Any = Field(default=None) + scale: Union[None, float, int] = Field(default=None) + minimumPixelSize: Union[None, float, int] = Field(default=None) + maximumScale: Union[None, float, int] = Field(default=None) + incrementallyLoadTextures: Union[None, bool] = Field(default=None) + runAnimations: Union[None, bool] = Field(default=None) + shadows: Union[None, ShadowMode] = Field(default=None) + heightReference: Union[None, HeightReference] = Field(default=None) + silhouetteColor: Union[None, Color, str] = Field(default=None) + silhouetteSize: Union[None, Color, str] = Field(default=None) + color: Union[None, Color, str] = Field(default=None) + colorBlendMode: Union[None, ColorBlendMode] = Field(default=None) + colorBlendAmount: Union[None, float, int] = Field(default=None) + distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + default=None + ) + nodeTransformations: Union[None, Any] = Field(default=None) + articulations: Union[None, Any] = Field(default=None) class Uri(BaseCZMLObject, Deletable): @@ -825,7 +853,7 @@ class Uri(BaseCZMLObject, Deletable): The URI can optionally vary with time. """ - uri: None | str = Field(default=None) + uri: Union[None, str] = Field(default=None) @field_validator("uri") @classmethod @@ -839,5 +867,5 @@ def _check_uri(cls, value: str): return value @model_serializer - def custom_serializer(self) -> None | str: + def custom_serializer(self) -> Union[None, str]: return self.uri diff --git a/src/czml3/types.py b/src/czml3/types.py index dd30f1a..edb309f 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -1,6 +1,7 @@ import datetime as dt import re -from typing import Any, Self +import sys +from typing import Any, Dict, List, Union from dateutil.parser import isoparse as parse_iso_date from pydantic import ( @@ -13,6 +14,11 @@ from .base import BaseCZMLObject from .constants import ISO8601_FORMAT_Z +if sys.version_info[1] >= 11: + from typing import Self +else: + from typing_extensions import Self + TYPE_MAPPING = {bool: "boolean"} @@ -137,7 +143,7 @@ class RgbafValue(BaseCZMLObject): """ - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -177,7 +183,7 @@ class RgbaValue(BaseCZMLObject): """ - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -240,7 +246,7 @@ class Cartesian3Value(BaseCZMLObject): """ - values: None | list[Any] = Field(default=None) + values: Union[None, List[Any]] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -257,7 +263,7 @@ def _check_values(self) -> Self: return self @model_serializer - def custom_serializer(self) -> list[Any]: + def custom_serializer(self) -> List[Any]: if self.values is None: return [] return list(self.values) @@ -273,7 +279,7 @@ class Cartesian2Value(BaseCZMLObject): """ - values: None | list[Any] = Field(default=None) + values: Union[None, List[Any]] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -307,7 +313,7 @@ class CartographicRadiansValue(BaseCZMLObject): """ - values: None | list[Any] = Field(default=None) + values: Union[None, List[Any]] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -341,7 +347,7 @@ class CartographicDegreesValue(BaseCZMLObject): """ - values: None | list[Any] = Field(default=None) + values: Union[None, List[Any]] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -358,7 +364,7 @@ def _check_values(self) -> Self: return self @model_serializer - def custom_serializer(self) -> list[Any]: + def custom_serializer(self) -> List[Any]: if self.values is None: return [] return self.values @@ -381,7 +387,7 @@ class CartographicRadiansListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in radians and Height is in meters.""" - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -401,7 +407,7 @@ class CartographicDegreesListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in degrees and Height is in meters.""" - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -424,7 +430,7 @@ class DistanceDisplayConditionValue(BaseCZMLObject): where Time is an ISO 8601 date and time string or seconds since epoch. """ - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -448,7 +454,7 @@ class NearFarScalarValue(BaseCZMLObject): FarDistance, FarValue, ...], where Time is an ISO 8601 date and time string or seconds since epoch. """ - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -470,8 +476,8 @@ def custom_serializer(self): class TimeInterval(BaseCZMLObject): """A time interval, specified in ISO8601 interval format.""" - start: str | dt.datetime = Field(default="0001-01-01T00:00:00Z") - end: str | dt.datetime = Field(default="9999-12-31T23:59:59Z") + start: Union[str, dt.datetime] = Field(default="0001-01-01T00:00:00Z") + end: Union[str, dt.datetime] = Field(default="9999-12-31T23:59:59Z") @field_validator("start", "end") @classmethod @@ -486,12 +492,12 @@ def custom_serializer(self) -> str: class IntervalValue(BaseCZMLObject): """Value over some interval.""" - start: str | dt.datetime - end: str | dt.datetime - value: Any = Field(default=None) + start: Union[str, dt.datetime] + end: Union[str, dt.datetime] + value: Union[Any] = Field(default=None) @model_serializer - def custom_serializer(self) -> dict[str, Any]: + def custom_serializer(self) -> Dict[str, Any]: obj_dict = { "interval": TimeInterval(start=self.start, end=self.end).model_dump( exclude_none=True @@ -515,10 +521,10 @@ def custom_serializer(self) -> dict[str, Any]: class Sequence(BaseCZMLObject): """Sequence, list, array of objects.""" - values: list[Any] + values: List[Any] @model_serializer - def custom_serializer(self) -> list[Any]: + def custom_serializer(self) -> List[Any]: return list(self.values) @@ -532,7 +538,7 @@ class UnitQuaternionValue(BaseCZMLObject): """ - values: list[float] | list[int] + values: Union[List[float], List[int]] @model_validator(mode="after") def _check_values(self) -> Self: @@ -551,7 +557,7 @@ def custom_serializer(self): class EpochValue(BaseCZMLObject): """A value representing a time epoch.""" - value: str | dt.datetime + value: Union[str, dt.datetime] @model_serializer def custom_serializer(self): @@ -561,7 +567,7 @@ def custom_serializer(self): class NumberValue(BaseCZMLObject): """A single number, or a list of number pairs signifying the time and representative value.""" - values: int | float | list[float] | list[int] + values: Union[int, float, List[float], List[int]] @model_serializer def custom_serializer(self): From afcfb7a4a4bb6e0e35a5ee8dbdc905dad16b9cdb Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Tue, 26 Nov 2024 14:00:18 +0200 Subject: [PATCH 12/20] Drop Python 3.7 support --- .github/workflows/workflow.yml | 2 +- README.rst | 2 +- pyproject.toml | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index bb23c9f..3184c72 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/README.rst b/README.rst index 0bbaaea..5063382 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,7 @@ or conda:: $ conda install czml3 --channel conda-forge -czml3 requires Python >= 3.7. +czml3 requires Python >= 3.8. Examples ======== diff --git a/pyproject.toml b/pyproject.toml index e700a14..a247787 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,11 +17,10 @@ select = [ [tool.tox] legacy_tox_ini = """ [tox] - envlist = quality, test, pypy, pypy3, py{37,38,39,310,311,312} + envlist = quality, test, pypy, pypy3, py{38,39,310,311,312} [gh-actions] python = - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 @@ -32,7 +31,6 @@ legacy_tox_ini = """ basepython = pypy: {env:PYTHON:pypy} pypy3: {env:PYTHON:pypy3} - py37: {env:PYTHON:python3.7} py38: {env:PYTHON:python3.8} py39: {env:PYTHON:python3.9} py310: {env:PYTHON:python3.10} @@ -83,7 +81,7 @@ authors = [ ] description = "Python 3 library to write CZML" readme = "README.rst" -requires-python = ">=3.7" +requires-python = ">=3.8" keywords = ["czml", "cesium", "orbits"] license = {text = "MIT"} classifiers = [ @@ -93,7 +91,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", From 6fb604b620ce2098afef9359b8b612ec5bfa5460 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Tue, 26 Nov 2024 14:03:18 +0200 Subject: [PATCH 13/20] Add Python 3.13 support --- .github/workflows/workflow.yml | 2 +- pyproject.toml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 3184c72..599e1a4 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/pyproject.toml b/pyproject.toml index a247787..7311be8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ select = [ [tool.tox] legacy_tox_ini = """ [tox] - envlist = quality, test, pypy, pypy3, py{38,39,310,311,312} + envlist = quality, test, pypy, pypy3, py{38,39,310,311,312,313} [gh-actions] python = @@ -26,6 +26,7 @@ legacy_tox_ini = """ 3.10: py310 3.11: py311, quality, test, pypy, pypy3 3.12: py312 + 3.13: py313 [testenv] basepython = @@ -36,6 +37,7 @@ legacy_tox_ini = """ py310: {env:PYTHON:python3.10} py311: {env:PYTHON:python3.11} py312: {env:PYTHON:python3.12} + py313: {env:PYTHON:python3.13} {quality,reformat,test,coverage}: {env:PYTHON:python3} setenv = PYTHONUNBUFFERED = yes @@ -96,6 +98,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Physics", From 8c05619cf0c27be43ebec4e334fec93d4a8d1747 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Tue, 26 Nov 2024 14:11:03 +0200 Subject: [PATCH 14/20] Add tests --- tests/test_widget.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_widget.py b/tests/test_widget.py index d358cf5..2a97007 100644 --- a/tests/test_widget.py +++ b/tests/test_widget.py @@ -20,3 +20,8 @@ def test_to_html_contains_script(): widget = CZMLWidget() assert widget.build_script() in widget.to_html() + + +def test_repr(): + widget = CZMLWidget() + assert widget.to_html() == widget._repr_html_() From 0a034b01936a4c849d5636ed53612036ed86be34 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Tue, 26 Nov 2024 14:22:25 +0200 Subject: [PATCH 15/20] Add test_material_image_uri() --- tests/test_properties.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_properties.py b/tests/test_properties.py index 43385eb..55342ee 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -431,6 +431,37 @@ def test_material_image(): ) assert str(mat) == expected_result + +def test_material_image_uri(): + expected_result = """{ + "image": { + "image": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + "repeat": [ + 2, + 2 + ], + "color": { + "rgba": [ + 200, + 100, + 30, + 255 + ] + } + } +}""" + + mat = Material( + image=ImageMaterial( + image=Uri( + uri="""data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7""" + ), + repeat=[2, 2], + color=Color(rgba=[200, 100, 30]), + ) + ) + assert str(mat) == expected_result + pol_mat = PolylineMaterial( image=ImageMaterial( image=Uri(uri="https://site.com/image.png"), From 17832ec05a1daab3aafde173af35d2d9f69dc230 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Tue, 26 Nov 2024 14:25:13 +0200 Subject: [PATCH 16/20] Fix test_material_image_uri() --- tests/test_properties.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_properties.py b/tests/test_properties.py index 55342ee..8f7ac15 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -454,7 +454,7 @@ def test_material_image_uri(): mat = Material( image=ImageMaterial( image=Uri( - uri="""data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7""" + uri="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" ), repeat=[2, 2], color=Color(rgba=[200, 100, 30]), @@ -462,15 +462,6 @@ def test_material_image_uri(): ) assert str(mat) == expected_result - pol_mat = PolylineMaterial( - image=ImageMaterial( - image=Uri(uri="https://site.com/image.png"), - repeat=[2, 2], - color=Color(rgba=[200, 100, 30]), - ) - ) - assert str(pol_mat) == expected_result - def test_material_grid(): expected_result = """{ From 9ece1ac9631475c0dbf1515081649d14fa4a5477 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Wed, 27 Nov 2024 12:56:09 +0200 Subject: [PATCH 17/20] Drop Python 3.8 support --- .github/workflows/workflow.yml | 2 +- pyproject.toml | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 599e1a4..80aba49 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/pyproject.toml b/pyproject.toml index 7311be8..4dcb791 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,11 +17,10 @@ select = [ [tool.tox] legacy_tox_ini = """ [tox] - envlist = quality, test, pypy, pypy3, py{38,39,310,311,312,313} + envlist = quality, test, pypy, pypy3, py{39,310,311,312,313} [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311, quality, test, pypy, pypy3 @@ -32,7 +31,6 @@ legacy_tox_ini = """ basepython = pypy: {env:PYTHON:pypy} pypy3: {env:PYTHON:pypy3} - py38: {env:PYTHON:python3.8} py39: {env:PYTHON:python3.9} py310: {env:PYTHON:python3.10} py311: {env:PYTHON:python3.11} @@ -83,7 +81,7 @@ authors = [ ] description = "Python 3 library to write CZML" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" keywords = ["czml", "cesium", "orbits"] license = {text = "MIT"} classifiers = [ @@ -93,7 +91,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From deb29dc68e1b47a784f258dbd02f0111f7506b7d Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Wed, 27 Nov 2024 13:01:45 +0200 Subject: [PATCH 18/20] mypy fixes --- pyproject.toml | 6 +- src/czml3/base.py | 4 +- src/czml3/common.py | 11 +- src/czml3/core.py | 64 ++-- src/czml3/enums.py | 4 +- src/czml3/properties.py | 628 +++++++++++++++++++--------------------- src/czml3/types.py | 48 +-- 7 files changed, 368 insertions(+), 397 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4dcb791..3acf427 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,5 @@ [tool.ruff.lint] -ignore = [ - "E501", - "UP007", # support Pythons <3.11 - "UP006", # support Pythons <3.11 -] +ignore = ["E501"] select = [ "E", # pycodestyle "F", # Pyflakes diff --git a/src/czml3/base.py b/src/czml3/base.py index 88ed282..1723f99 100644 --- a/src/czml3/base.py +++ b/src/czml3/base.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from pydantic import BaseModel, model_validator @@ -8,7 +8,7 @@ class BaseCZMLObject(BaseModel): @model_validator(mode="before") @classmethod - def check_model_before(cls, data: Dict[str, Any]) -> Any: + def check_model_before(cls, data: dict[str, Any]) -> Any: if data is not None and "delete" in data and data["delete"]: return { "delete": True, diff --git a/src/czml3/common.py b/src/czml3/common.py index ea82a94..f038b57 100644 --- a/src/czml3/common.py +++ b/src/czml3/common.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import datetime as dt -from typing import Union from pydantic import BaseModel, field_validator @@ -10,7 +11,7 @@ class Deletable(BaseModel): """A property whose value may be deleted.""" - delete: Union[None, bool] = None + delete: None | bool = None class Interpolatable(BaseModel): @@ -19,9 +20,9 @@ class Interpolatable(BaseModel): The interpolation happens over provided time-tagged samples. """ - epoch: Union[None, str, dt.datetime] = None - interpolationAlgorithm: Union[None, InterpolationAlgorithms] = None - interpolationDegree: Union[None, int] = None + epoch: None | str | dt.datetime = None + interpolationAlgorithm: None | InterpolationAlgorithms = None + interpolationDegree: None | int = None @field_validator("epoch") @classmethod diff --git a/src/czml3/core.py b/src/czml3/core.py index 86eaf6b..8c72b52 100644 --- a/src/czml3/core.py +++ b/src/czml3/core.py @@ -1,4 +1,6 @@ -from typing import Any, List, Union +from __future__ import annotations + +from typing import Any from uuid import uuid4 from pydantic import Field, model_serializer @@ -35,11 +37,11 @@ class Preamble(BaseCZMLObject): """The preamble packet.""" - id: Union[str] = Field(default="document") - version: Union[str] = Field(default=CZML_VERSION) - name: Union[None, str] = Field(default=None) - description: Union[None, str] = Field(default=None) - clock: Union[None, Clock, IntervalValue] = Field(default=None) + id: str = Field(default="document") + version: str = Field(default=CZML_VERSION) + name: None | str = Field(default=None) + description: None | str = Field(default=None) + clock: None | Clock | IntervalValue = Field(default=None) class Packet(BaseCZMLObject): @@ -50,38 +52,38 @@ class Packet(BaseCZMLObject): """ id: str = Field(default=str(uuid4())) - delete: Union[None, bool] = Field(default=None) - name: Union[None, str] = Field(default=None) - parent: Union[None, str] = Field(default=None) - description: Union[None, str, StringValue] = Field(default=None) - availability: Union[None, TimeInterval, List[TimeInterval], Sequence] = Field( + delete: None | bool = Field(default=None) + name: None | str = Field(default=None) + parent: None | str = Field(default=None) + description: None | str | StringValue = Field(default=None) + availability: None | TimeInterval | list[TimeInterval] | Sequence = Field( default=None ) - properties: Union[None, Any] = Field(default=None) - position: Union[None, Position] = Field(default=None) - orientation: Union[None, Orientation] = Field(default=None) - viewFrom: Union[None, ViewFrom] = Field(default=None) - billboard: Union[None, Billboard] = Field(default=None) - box: Union[None, Box] = Field(default=None) - corridor: Union[None, Corridor] = Field(default=None) - cylinder: Union[None, Cylinder] = Field(default=None) - ellipse: Union[None, Ellipse] = Field(default=None) - ellipsoid: Union[None, Ellipsoid] = Field(default=None) - label: Union[None, Label] = Field(default=None) - model: Union[None, Model] = Field(default=None) - path: Union[None, Path] = Field(default=None) - point: Union[None, Point] = Field(default=None) - polygon: Union[None, Polygon] = Field(default=None) - polyline: Union[None, Polyline] = Field(default=None) - rectangle: Union[None, Rectangle] = Field(default=None) - tileset: Union[None, Tileset] = Field(default=None) - wall: Union[None, Wall] = Field(default=None) + properties: None | Any = Field(default=None) + position: None | Position = Field(default=None) + orientation: None | Orientation = Field(default=None) + viewFrom: None | ViewFrom = Field(default=None) + billboard: None | Billboard = Field(default=None) + box: None | Box = Field(default=None) + corridor: None | Corridor = Field(default=None) + cylinder: None | Cylinder = Field(default=None) + ellipse: None | Ellipse = Field(default=None) + ellipsoid: None | Ellipsoid = Field(default=None) + label: None | Label = Field(default=None) + model: None | Model = Field(default=None) + path: None | Path = Field(default=None) + point: None | Point = Field(default=None) + polygon: None | Polygon = Field(default=None) + polyline: None | Polyline = Field(default=None) + rectangle: None | Rectangle = Field(default=None) + tileset: None | Tileset = Field(default=None) + wall: None | Wall = Field(default=None) class Document(BaseCZMLObject): """A CZML document, consisting on a list of packets.""" - packets: List[Union[Packet, Preamble]] + packets: list[Packet | Preamble] @model_serializer def custom_serializer(self): diff --git a/src/czml3/enums.py b/src/czml3/enums.py index f1233f8..631e404 100644 --- a/src/czml3/enums.py +++ b/src/czml3/enums.py @@ -1,6 +1,6 @@ import sys from enum import auto -from typing import Any, List +from typing import Any if sys.version_info[1] >= 11: from enum import StrEnum @@ -12,7 +12,7 @@ class OCaseStrEnum(StrEnum): @staticmethod def _generate_next_value_( - name: str, start: int, count: int, last_values: List[Any] + name: str, start: int, count: int, last_values: list[Any] ) -> str: return name else: diff --git a/src/czml3/properties.py b/src/czml3/properties.py index 678f8b8..abaf5c3 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -1,7 +1,7 @@ from __future__ import annotations import datetime as dt -from typing import Any, List, Union +from typing import Any from pydantic import ( BaseModel, @@ -48,19 +48,19 @@ class HasAlignment(BaseModel): """A property that can be horizontally or vertically aligned.""" - horizontalOrigin: Union[None, HorizontalOrigins] = Field(default=None) - verticalOrigin: Union[None, VerticalOrigins] = Field(default=None) + horizontalOrigin: None | HorizontalOrigins = Field(default=None) + verticalOrigin: None | VerticalOrigins = Field(default=None) class Material(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - solidColor: Union[None, Color, SolidColorMaterial, str] = Field(default=None) - image: Union[None, ImageMaterial, str, Uri] = Field(default=None) - grid: Union[None, GridMaterial] = Field(default=None) - stripe: Union[None, StripeMaterial] = Field(default=None) - checkerboard: Union[None, CheckerboardMaterial] = Field(default=None) - polylineOutline: Union[None, PolylineMaterial] = Field( + solidColor: None | Color | SolidColorMaterial | str = Field(default=None) + image: None | ImageMaterial | str | Uri = Field(default=None) + grid: None | GridMaterial = Field(default=None) + stripe: None | StripeMaterial = Field(default=None) + checkerboard: None | CheckerboardMaterial = Field(default=None) + polylineOutline: None | PolylineMaterial = Field( default=None ) # NOTE: Not present in documentation @@ -68,117 +68,117 @@ class Material(BaseCZMLObject): class PolylineOutline(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - color: Union[None, Color, str] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, int, float] = Field(default=None) + color: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | int | float = Field(default=None) class PolylineOutlineMaterial(BaseCZMLObject): """A definition of the material wrapper for a polyline outline.""" - polylineOutline: Union[None, PolylineOutline] = Field(default=None) + polylineOutline: None | PolylineOutline = Field(default=None) class PolylineGlow(BaseCZMLObject): """A definition of how a glowing polyline appears.""" - color: Union[None, Color, str] = Field(default=None) - glowPower: Union[None, float, int] = Field(default=None) - taperPower: Union[None, float, int] = Field(default=None) + color: None | Color | str = Field(default=None) + glowPower: None | float | int = Field(default=None) + taperPower: None | float | int = Field(default=None) class PolylineGlowMaterial(BaseCZMLObject): """A material that fills the surface of a line with a glowing color.""" - polylineGlow: Union[None, PolylineGlow] = Field(default=None) + polylineGlow: None | PolylineGlow = Field(default=None) class PolylineArrow(BaseCZMLObject): """A definition of how a polyline arrow appears.""" - color: Union[None, Color, str] = Field(default=None) + color: None | Color | str = Field(default=None) class PolylineArrowMaterial(BaseCZMLObject): """A material that fills the surface of a line with an arrow.""" - polylineArrow: Union[None, PolylineArrow] = Field(default=None) + polylineArrow: None | PolylineArrow = Field(default=None) class PolylineDash(BaseCZMLObject): """A definition of how a polyline should be dashed with two colors.""" - color: Union[None, Color, str] = Field(default=None) - gapColor: Union[None, Color, str] = Field(default=None) - dashLength: Union[None, float, int] = Field(default=None) - dashPattern: Union[None, int] = Field(default=None) + color: None | Color | str = Field(default=None) + gapColor: None | Color | str = Field(default=None) + dashLength: None | float | int = Field(default=None) + dashPattern: None | int = Field(default=None) class PolylineDashMaterial(BaseCZMLObject): """A material that provides a how a polyline should be dashed.""" - polylineDash: Union[None, PolylineDash] = Field(default=None) + polylineDash: None | PolylineDash = Field(default=None) class PolylineMaterial(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - solidColor: Union[None, SolidColorMaterial, str] = Field(default=None) - image: Union[None, ImageMaterial, str, Uri] = Field(default=None) - grid: Union[None, GridMaterial] = Field(default=None) - stripe: Union[None, StripeMaterial] = Field(default=None) - checkerboard: Union[None, CheckerboardMaterial] = Field(default=None) - polylineDash: Union[None, PolylineDashMaterial] = Field(default=None) + solidColor: None | SolidColorMaterial | str = Field(default=None) + image: None | ImageMaterial | str | Uri = Field(default=None) + grid: None | GridMaterial = Field(default=None) + stripe: None | StripeMaterial = Field(default=None) + checkerboard: None | CheckerboardMaterial = Field(default=None) + polylineDash: None | PolylineDashMaterial = Field(default=None) class SolidColorMaterial(BaseCZMLObject): """A material that fills the surface with a solid color.""" - color: Union[None, Color, str] = Field(default=None) + color: None | Color | str = Field(default=None) class GridMaterial(BaseCZMLObject): """A material that fills the surface with a two-dimensional grid.""" - color: Union[None, Color, str] = Field(default=None) - cellAlpha: Union[None, float, int] = Field(default=None) - lineCount: Union[None, List[int]] = Field(default=None) - lineThickness: Union[None, List[float], List[int]] = Field(default=None) - lineOffset: Union[None, List[float], List[int]] = Field(default=None) + color: None | Color | str = Field(default=None) + cellAlpha: None | float | int = Field(default=None) + lineCount: None | list[int] = Field(default=None) + lineThickness: None | list[float] | list[int] = Field(default=None) + lineOffset: None | list[float] | list[int] = Field(default=None) class StripeMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - orientation: Union[None, int] = Field(default=None) - evenColor: Union[None, Color, str] = Field(default=None) - oddColor: Union[None, Color, str] = Field(default=None) - offset: Union[None, float, int] = Field(default=None) - repeat: Union[None, float, int] = Field(default=None) + orientation: None | int = Field(default=None) + evenColor: None | Color | str = Field(default=None) + oddColor: None | Color | str = Field(default=None) + offset: None | float | int = Field(default=None) + repeat: None | float | int = Field(default=None) class CheckerboardMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - evenColor: Union[None, Color, str] = Field(default=None) - oddColor: Union[None, Color, str] = Field(default=None) - repeat: Union[None, int] = Field(default=None) + evenColor: None | Color | str = Field(default=None) + oddColor: None | Color | str = Field(default=None) + repeat: None | int = Field(default=None) class ImageMaterial(BaseCZMLObject): """A material that fills the surface with an image.""" - image: Union[None, ImageMaterial, str, Uri] = Field(default=None) - repeat: Union[None, List[int]] = Field(default=None) - color: Union[None, Color, str] = Field(default=None) - transparent: Union[None, bool] = Field(default=None) + image: None | ImageMaterial | str | Uri = Field(default=None) + repeat: None | list[int] = Field(default=None) + color: None | Color | str = Field(default=None) + transparent: None | bool = Field(default=None) class Color(BaseCZMLObject, Interpolatable, Deletable): """A color. The color can optionally vary over time.""" - rgba: Union[None, RgbaValue, str, List[float], List[int]] = Field(default=None) - rgbaf: Union[None, RgbafValue, str, List[float], List[int]] = Field(default=None) + rgba: None | RgbaValue | str | list[float] | list[int] = Field(default=None) + rgbaf: None | RgbafValue | str | list[float] | list[int] = Field(default=None) @field_validator("rgba", "rgbaf") @classmethod @@ -225,16 +225,14 @@ def is_valid(cls, color): class Position(BaseCZMLObject, Interpolatable, Deletable): """Defines a position. The position can optionally vary over time.""" - referenceFrame: Union[None, str] = Field(default=None) - cartesian: Union[None, Cartesian3Value, List[float], List[int]] = Field( - default=None - ) - cartographicRadians: Union[None, List[float], List[int]] = Field(default=None) - cartographicDegrees: Union[None, List[float], List[int]] = Field(default=None) - cartesianVelocity: Union[None, List[float], List[int]] = Field(default=None) - reference: Union[None, str] = Field(default=None) - interval: Union[None, TimeInterval] = Field(default=None) - epoch: Union[None, str, dt.datetime] = Field(default=None) + referenceFrame: None | str = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) + cartographicRadians: None | list[float] | list[int] = Field(default=None) + cartographicDegrees: None | list[float] | list[int] = Field(default=None) + cartesianVelocity: None | list[float] | list[int] = Field(default=None) + reference: None | str = Field(default=None) + interval: None | TimeInterval = Field(default=None) + epoch: None | str | dt.datetime = Field(default=None) @model_validator(mode="after") def checks(self): @@ -274,8 +272,8 @@ class ViewFrom(BaseCZMLObject, Interpolatable, Deletable): ViewFrom can optionally vary over time.""" - cartesian: Union[None, Cartesian3Value, List[float], List[int]] - reference: Union[None, str] = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -291,19 +289,19 @@ class Billboard(BaseCZMLObject, HasAlignment): A billboard is sometimes called a marker. """ - image: Union[str, Uri] - show: Union[None, bool] = Field(default=None) - scale: Union[None, float, int] = Field(default=None) - pixelOffset: Union[None, List[float], List[int]] = Field(default=None) - eyeOffset: Union[None, List[float], List[int]] = Field(default=None) - color: Union[None, Color, str] = Field(default=None) + image: str | Uri + show: None | bool = Field(default=None) + scale: None | float | int = Field(default=None) + pixelOffset: None | list[float] | list[int] = Field(default=None) + eyeOffset: None | list[float] | list[int] = Field(default=None) + color: None | Color | str = Field(default=None) class EllipsoidRadii(BaseCZMLObject, Interpolatable, Deletable): """The radii of an ellipsoid.""" - cartesian: Union[None, Cartesian3Value, List[float], List[int]] - reference: Union[None, str] = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -316,140 +314,130 @@ class Corridor(BaseCZMLObject): """A corridor , which is a shape defined by a centerline and width that conforms to the curvature of the body shape. It can can optionally be extruded into a volume.""" - positions: Union[PositionList, List[int], List[float]] - show: Union[None, bool] = Field(default=None) - width: Union[float, int] - height: Union[None, float, int] = Field(default=None) - heightReference: Union[None, HeightReference] = Field(default=None) - extrudedHeight: Union[None, float, int] = Field(default=None) - extrudedHeightReference: Union[None, HeightReference] = Field(default=None) - cornerType: Union[None, CornerType] = Field(default=None) - granularity: Union[None, float, int] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - outline: Union[None, Color, str] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, int, float] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) - classificationType: Union[None, ClassificationType] = Field(default=None) - zIndex: Union[None, int] = Field(default=None) + positions: PositionList | list[int] | list[float] + show: None | bool = Field(default=None) + width: float | int + height: None | float | int = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + extrudedHeight: None | float | int = Field(default=None) + extrudedHeightReference: None | HeightReference = Field(default=None) + cornerType: None | CornerType = Field(default=None) + granularity: None | float | int = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | int | float = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) class Cylinder(BaseCZMLObject): """A cylinder, which is a special cone defined by length, top and bottom radius.""" - length: Union[float, int] - show: Union[None, bool] = Field(default=None) - topRadius: Union[float, int] - bottomRadius: Union[float, int] - heightReference: Union[None, HeightReference] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - outline: Union[None, bool] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - numberOfVerticalLines: Union[None, int] = Field(default=None) - slices: Union[None, int] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) + length: float | int + show: None | bool = Field(default=None) + topRadius: float | int + bottomRadius: float | int + heightReference: None | HeightReference = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + numberOfVerticalLines: None | int = Field(default=None) + slices: None | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) class Ellipse(BaseCZMLObject): """An ellipse, which is a close curve, on or above Earth's surface.""" - semiMajorAxis: Union[float, int] - semiMinorAxis: Union[float, int] - show: Union[None, bool] = Field(default=None) - height: Union[None, float, int] = Field(default=None) - heightReference: Union[None, HeightReference] = Field(default=None) - extrudedHeight: Union[None, float, int] = Field(default=None) - extrudedHeightReference: Union[None, HeightReference] = Field(default=None) - rotation: Union[None, float, int] = Field(default=None) - stRotation: Union[None, float, int] = Field(default=None) - granularity: Union[None, float, int] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - outline: Union[None, bool] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - numberOfVerticalLines: Union[None, int] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) - classificationType: Union[None, ClassificationType] = Field(default=None) - zIndex: Union[None, int] = Field(default=None) + semiMajorAxis: float | int + semiMinorAxis: float | int + show: None | bool = Field(default=None) + height: None | float | int = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + extrudedHeight: None | float | int = Field(default=None) + extrudedHeightReference: None | HeightReference = Field(default=None) + rotation: None | float | int = Field(default=None) + stRotation: None | float | int = Field(default=None) + granularity: None | float | int = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + numberOfVerticalLines: None | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) class Polygon(BaseCZMLObject): """A polygon, which is a closed figure on the surface of the Earth.""" - positions: Union[Position, PositionList, List[int], List[float]] - show: Union[None, bool] = Field(default=None) - arcType: Union[None, ArcType] = Field(default=None) - granularity: Union[None, float, int] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( + positions: Position | PositionList | list[int] | list[float] + show: None | bool = Field(default=None) + arcType: None | ArcType = Field(default=None) + granularity: None | float | int = Field(default=None) + material: None | Material | str = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) + holes: None | PositionList | PositionListOfLists | list[int] | list[float] = Field( default=None - ) - classificationType: Union[None, ClassificationType] = Field(default=None) - zIndex: Union[None, int] = Field(default=None) - holes: Union[None, PositionList, PositionListOfLists, List[int], List[float]] = ( - Field(default=None) ) # NOTE: not in documentation - outlineColor: Union[None, Color, str] = Field(default=None) - outline: Union[None, bool] = Field(default=None) - extrudedHeight: Union[None, float, int] = Field(default=None) - perPositionHeight: Union[None, bool] = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outline: None | bool = Field(default=None) + extrudedHeight: None | float | int = Field(default=None) + perPositionHeight: None | bool = Field(default=None) class Polyline(BaseCZMLObject): """A polyline, which is a line in the scene composed of multiple segments.""" positions: PositionList - show: Union[None, bool] = Field(default=None) - arcType: Union[None, ArcType] = Field(default=None) - width: Union[None, float, int] = Field(default=None) - granularity: Union[None, float, int] = Field(default=None) - material: Union[ - None, - PolylineMaterial, - PolylineDashMaterial, - PolylineArrowMaterial, - PolylineGlowMaterial, - PolylineOutlineMaterial, - str, - ] = Field(default=None) - followSurface: Union[None, bool] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - depthFailMaterial: Union[ - None, - PolylineMaterial, - PolylineDashMaterial, - PolylineArrowMaterial, - PolylineGlowMaterial, - PolylineOutlineMaterial, - str, - ] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) - clampToGround: Union[None, bool] = Field(default=None) - classificationType: Union[None, ClassificationType] = Field(default=None) - zIndex: Union[None, int] = Field(default=None) + show: None | bool = Field(default=None) + arcType: None | ArcType = Field(default=None) + width: None | float | int = Field(default=None) + granularity: None | float | int = Field(default=None) + material: ( + None + | PolylineMaterial + | PolylineDashMaterial + | PolylineArrowMaterial + | PolylineGlowMaterial + | PolylineOutlineMaterial + | str + ) = Field(default=None) + followSurface: None | bool = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + depthFailMaterial: ( + None + | PolylineMaterial + | PolylineDashMaterial + | PolylineArrowMaterial + | PolylineGlowMaterial + | PolylineOutlineMaterial + | str + ) = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + clampToGround: None | bool = Field(default=None) + classificationType: None | ClassificationType = Field(default=None) + zIndex: None | int = Field(default=None) class ArcType(BaseCZMLObject, Deletable): """The type of an arc.""" - arcType: Union[None, ArcTypes, str] = Field(default=None) - reference: Union[None, str] = Field(default=None) + arcType: None | ArcTypes | str = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -461,8 +449,8 @@ def check(cls, r): class ShadowMode(BaseCZMLObject, Deletable): """Whether or not an object casts or receives shadows from each light source when shadows are enabled.""" - shadowMode: Union[None, ShadowModes] = Field(default=None) - reference: Union[None, str] = Field(default=None) + shadowMode: None | ShadowModes = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -474,8 +462,8 @@ def check(cls, r): class ClassificationType(BaseCZMLObject, Deletable): """Whether a classification affects terrain, 3D Tiles, or both.""" - classificationType: Union[None, ClassificationTypes] = Field(default=None) - reference: Union[None, str] = Field(default=None) + classificationType: None | ClassificationTypes = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -487,10 +475,8 @@ def check(cls, r): class DistanceDisplayCondition(BaseCZMLObject, Interpolatable, Deletable): """Indicates the visibility of an object based on the distance to the camera.""" - distanceDisplayCondition: Union[None, DistanceDisplayConditionValue] = Field( - default=None - ) - reference: Union[None, str] = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayConditionValue = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -502,33 +488,31 @@ def check(cls, r): class PositionListOfLists(BaseCZMLObject, Deletable): """A list of positions.""" - referenceFrame: Union[None, str, List[str]] = Field(default=None) - cartesian: Union[None, Cartesian3Value] = Field(default=None) - cartographicRadians: Union[ - None, List[float], List[int], List[List[float]], List[List[int]] - ] = Field(default=None) - cartographicDegrees: Union[ - None, List[float], List[int], List[List[float]], List[List[int]] - ] = Field(default=None) - references: Union[None, str, List[str]] = Field(default=None) + referenceFrame: None | str | list[str] = Field(default=None) + cartesian: None | Cartesian3Value = Field(default=None) + cartographicRadians: ( + None | list[float] | list[int] | list[list[float]] | list[list[int]] + ) = Field(default=None) + cartographicDegrees: ( + None | list[float] | list[int] | list[list[float]] | list[list[int]] + ) = Field(default=None) + references: None | str | list[str] = Field(default=None) class PositionList(BaseCZMLObject, Interpolatable, Deletable): """A list of positions.""" - referenceFrame: Union[None, str, List[str]] = Field(default=None) - cartesian: Union[None, Cartesian3Value, List[float], List[int]] = Field( - default=None - ) - cartographicRadians: Union[ - None, List[float], List[int], CartographicRadiansListValue - ] = Field(default=None) - cartographicDegrees: Union[ - None, List[float], List[int], CartographicDegreesListValue - ] = Field(default=None) - references: Union[None, str, List[str]] = Field(default=None) - interval: Union[None, TimeInterval] = Field(default=None) - epoch: Union[None, str, dt.datetime] = Field(default=None) # note: not documented + referenceFrame: None | str | list[str] = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) + cartographicRadians: ( + None | list[float] | list[int] | CartographicRadiansListValue + ) = Field(default=None) + cartographicDegrees: ( + None | list[float] | list[int] | CartographicDegreesListValue + ) = Field(default=None) + references: None | str | list[str] = Field(default=None) + interval: None | TimeInterval = Field(default=None) + epoch: None | str | dt.datetime = Field(default=None) # note: not documented @field_validator("epoch") @classmethod @@ -540,45 +524,43 @@ class Ellipsoid(BaseCZMLObject): """A closed quadric surface that is a three-dimensional analogue of an ellipse.""" radii: EllipsoidRadii - innerRadii: Union[None, EllipsoidRadii] = Field(default=None) - minimumClock: Union[None, float, int] = Field(default=None) - maximumClock: Union[None, float, int] = Field(default=None) - minimumCone: Union[None, float, int] = Field(default=None) - maximumCone: Union[None, float, int] = Field(default=None) - show: Union[None, bool] = Field(default=None) - heightReference: Union[None, HeightReference] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - outline: Union[None, bool] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - stackPartitions: Union[None, int] = Field(default=None) - slicePartitions: Union[None, int] = Field(default=None) - subdivisions: Union[None, int] = Field(default=None) + innerRadii: None | EllipsoidRadii = Field(default=None) + minimumClock: None | float | int = Field(default=None) + maximumClock: None | float | int = Field(default=None) + minimumCone: None | float | int = Field(default=None) + maximumCone: None | float | int = Field(default=None) + show: None | bool = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + stackPartitions: None | int = Field(default=None) + slicePartitions: None | int = Field(default=None) + subdivisions: None | int = Field(default=None) class Box(BaseCZMLObject): """A box, which is a closed rectangular cuboid.""" - show: Union[None, bool] = Field(default=None) - dimensions: Union[None, BoxDimensions] = Field(default=None) - heightReference: Union[None, HeightReference] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - outline: Union[None, bool] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) + show: None | bool = Field(default=None) + dimensions: None | BoxDimensions = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) class BoxDimensions(BaseCZMLObject, Interpolatable): """The width, depth, and height of a box.""" - cartesian: Union[None, Cartesian3Value] = Field(default=None) - reference: Union[None, str] = Field(default=None) + cartesian: None | Cartesian3Value = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -592,17 +574,17 @@ class Rectangle(BaseCZMLObject, Interpolatable, Deletable): can be placed on the surface or at altitude and can optionally be extruded into a volume. """ - coordinates: Union[None, RectangleCoordinates] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) + coordinates: None | RectangleCoordinates = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) class RectangleCoordinates(BaseCZMLObject, Interpolatable, Deletable): """A set of coordinates describing a cartographic rectangle on the surface of the ellipsoid.""" - wsen: Union[None, List[float], List[int]] = Field(default=None) - wsenDegrees: Union[None, List[float], List[int]] = Field(default=None) - reference: Union[None, str] = Field(default=None) + wsen: None | list[float] | list[int] = Field(default=None) + wsenDegrees: None | list[float] | list[int] = Field(default=None) + reference: None | str = Field(default=None) @model_validator(mode="after") def checks(self): @@ -628,10 +610,8 @@ class EyeOffset(BaseCZMLObject, Deletable): """ - cartesian: Union[None, Cartesian3Value, List[float], List[int]] = Field( - default=None - ) - reference: Union[None, str] = Field(default=None) + cartesian: None | Cartesian3Value | list[float] | list[int] = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -643,8 +623,8 @@ def check(cls, r): class HeightReference(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - heightReference: Union[None, HeightReferences] = Field(default=None) - reference: Union[None, str] = Field(default=None) + heightReference: None | HeightReferences = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -656,8 +636,8 @@ def check(cls, r): class ColorBlendMode(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - colorBlendMode: Union[None, ColorBlendModes] = Field(default=None) - reference: Union[None, str] = Field(default=None) + colorBlendMode: None | ColorBlendModes = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -669,8 +649,8 @@ def check(cls, r): class CornerType(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - cornerType: Union[None, CornerTypes] = Field(default=None) - reference: Union[None, str] = Field(default=None) + cornerType: None | CornerTypes = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -686,10 +666,10 @@ class Clock(BaseCZMLObject): """ - currentTime: Union[None, str, dt.datetime] = Field(default=None) - multiplier: Union[None, float, int] = Field(default=None) - range: Union[None, ClockRanges] = Field(default=None) - step: Union[None, ClockSteps] = Field(default=None) + currentTime: None | str | dt.datetime = Field(default=None) + multiplier: None | float | int = Field(default=None) + range: None | ClockRanges = Field(default=None) + step: None | ClockSteps = Field(default=None) @field_validator("currentTime") @classmethod @@ -708,40 +688,36 @@ class Path(BaseCZMLObject): """ - show: Union[None, bool, Sequence] = Field(default=None) - leadTime: Union[None, float, int] = Field(default=None) - trailTime: Union[None, float, int] = Field(default=None) - width: Union[None, float, int] = Field(default=None) - resolution: Union[None, float, int] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) + show: None | bool | Sequence = Field(default=None) + leadTime: None | float | int = Field(default=None) + trailTime: None | float | int = Field(default=None) + width: None | float | int = Field(default=None) + resolution: None | float | int = Field(default=None) + material: None | Material | str = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) class Point(BaseCZMLObject): """A point, or viewport-aligned circle.""" - show: Union[None, bool] = Field(default=None) - pixelSize: Union[None, float, int] = Field(default=None) - heightReference: Union[None, HeightReference] = Field(default=None) - color: Union[None, Color, str] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - scaleByDistance: Union[None, NearFarScalar] = Field(default=None) - translucencyByDistance: Union[None, NearFarScalar] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) - disableDepthTestDistance: Union[None, float, int] = Field(default=None) + show: None | bool = Field(default=None) + pixelSize: None | float | int = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + color: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + scaleByDistance: None | NearFarScalar = Field(default=None) + translucencyByDistance: None | NearFarScalar = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + disableDepthTestDistance: None | float | int = Field(default=None) class Tileset(BaseCZMLObject): """A 3D Tiles tileset.""" - uri: Union[str, Uri] - show: Union[None, bool] = Field(default=None) - maximumScreenSpaceError: Union[None, float, int] = Field(default=None) + uri: str | Uri + show: None | bool = Field(default=None) + maximumScreenSpaceError: None | float | int = Field(default=None) class Wall(BaseCZMLObject): @@ -749,20 +725,18 @@ class Wall(BaseCZMLObject): It conforms to the curvature of the globe and can be placed along the surface or at altitude. """ - show: Union[None, bool] = Field(default=None) + show: None | bool = Field(default=None) positions: PositionList - minimumHeights: Union[None, List[float], List[int]] = Field(default=None) - maximumHeights: Union[None, List[float], List[int]] = Field(default=None) - granularity: Union[None, float, int] = Field(default=None) - fill: Union[None, bool] = Field(default=None) - material: Union[None, Material, str] = Field(default=None) - outline: Union[None, bool] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) + minimumHeights: None | list[float] | list[int] = Field(default=None) + maximumHeights: None | list[float] | list[int] = Field(default=None) + granularity: None | float | int = Field(default=None) + fill: None | bool = Field(default=None) + material: None | Material | str = Field(default=None) + outline: None | bool = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): @@ -774,10 +748,10 @@ class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): less than the near distance or greater than the far distance, respectively. """ - nearFarScalar: Union[None, List[float], List[int], NearFarScalarValue] = Field( + nearFarScalar: None | list[float] | list[int] | NearFarScalarValue = Field( default=None ) - reference: Union[None, str] = Field(default=None) + reference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -789,17 +763,17 @@ def check(cls, r): class Label(BaseCZMLObject, HasAlignment): """A string of text.""" - show: Union[None, bool] = Field(default=None) - text: Union[None, str] = Field(default=None) - font: Union[None, str] = Field(default=None) - style: Union[None, LabelStyles] = Field(default=None) - scale: Union[None, float, int] = Field(default=None) - showBackground: Union[None, bool] = Field(default=None) - backgroundColor: Union[None, Color, str] = Field(default=None) - fillColor: Union[None, Color, str] = Field(default=None) - outlineColor: Union[None, Color, str] = Field(default=None) - outlineWidth: Union[None, float, int] = Field(default=None) - pixelOffset: Union[None, float, int, Cartesian2Value] = Field(default=None) + show: None | bool = Field(default=None) + text: None | str = Field(default=None) + font: None | str = Field(default=None) + style: None | LabelStyles = Field(default=None) + scale: None | float | int = Field(default=None) + showBackground: None | bool = Field(default=None) + backgroundColor: None | Color | str = Field(default=None) + fillColor: None | Color | str = Field(default=None) + outlineColor: None | Color | str = Field(default=None) + outlineWidth: None | float | int = Field(default=None) + pixelOffset: None | float | int | Cartesian2Value = Field(default=None) class Orientation(BaseCZMLObject, Interpolatable, Deletable): @@ -810,11 +784,11 @@ class Orientation(BaseCZMLObject, Interpolatable, Deletable): """ - unitQuaternion: Union[None, List[float], List[int], UnitQuaternionValue] = Field( + unitQuaternion: None | list[float] | list[int] | UnitQuaternionValue = Field( default=None ) - reference: Union[None, str] = Field(default=None) - velocityReference: Union[None, str] = Field(default=None) + reference: None | str = Field(default=None) + velocityReference: None | str = Field(default=None) @field_validator("reference") @classmethod @@ -826,25 +800,23 @@ def check(cls, r): class Model(BaseCZMLObject): """A 3D model.""" - show: Union[None, bool] = Field(default=None) + show: None | bool = Field(default=None) gltf: str - scale: Union[None, float, int] = Field(default=None) - minimumPixelSize: Union[None, float, int] = Field(default=None) - maximumScale: Union[None, float, int] = Field(default=None) - incrementallyLoadTextures: Union[None, bool] = Field(default=None) - runAnimations: Union[None, bool] = Field(default=None) - shadows: Union[None, ShadowMode] = Field(default=None) - heightReference: Union[None, HeightReference] = Field(default=None) - silhouetteColor: Union[None, Color, str] = Field(default=None) - silhouetteSize: Union[None, Color, str] = Field(default=None) - color: Union[None, Color, str] = Field(default=None) - colorBlendMode: Union[None, ColorBlendMode] = Field(default=None) - colorBlendAmount: Union[None, float, int] = Field(default=None) - distanceDisplayCondition: Union[None, DistanceDisplayCondition] = Field( - default=None - ) - nodeTransformations: Union[None, Any] = Field(default=None) - articulations: Union[None, Any] = Field(default=None) + scale: None | float | int = Field(default=None) + minimumPixelSize: None | float | int = Field(default=None) + maximumScale: None | float | int = Field(default=None) + incrementallyLoadTextures: None | bool = Field(default=None) + runAnimations: None | bool = Field(default=None) + shadows: None | ShadowMode = Field(default=None) + heightReference: None | HeightReference = Field(default=None) + silhouetteColor: None | Color | str = Field(default=None) + silhouetteSize: None | Color | str = Field(default=None) + color: None | Color | str = Field(default=None) + colorBlendMode: None | ColorBlendMode = Field(default=None) + colorBlendAmount: None | float | int = Field(default=None) + distanceDisplayCondition: None | DistanceDisplayCondition = Field(default=None) + nodeTransformations: None | Any = Field(default=None) + articulations: None | Any = Field(default=None) class Uri(BaseCZMLObject, Deletable): @@ -853,7 +825,7 @@ class Uri(BaseCZMLObject, Deletable): The URI can optionally vary with time. """ - uri: Union[None, str] = Field(default=None) + uri: None | str = Field(default=None) @field_validator("uri") @classmethod @@ -867,5 +839,5 @@ def _check_uri(cls, value: str): return value @model_serializer - def custom_serializer(self) -> Union[None, str]: + def custom_serializer(self) -> None | str: return self.uri diff --git a/src/czml3/types.py b/src/czml3/types.py index edb309f..876ea29 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -1,7 +1,7 @@ import datetime as dt import re import sys -from typing import Any, Dict, List, Union +from typing import Any from dateutil.parser import isoparse as parse_iso_date from pydantic import ( @@ -143,7 +143,7 @@ class RgbafValue(BaseCZMLObject): """ - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -183,7 +183,7 @@ class RgbaValue(BaseCZMLObject): """ - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -246,7 +246,7 @@ class Cartesian3Value(BaseCZMLObject): """ - values: Union[None, List[Any]] = Field(default=None) + values: None | list[Any] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -263,7 +263,7 @@ def _check_values(self) -> Self: return self @model_serializer - def custom_serializer(self) -> List[Any]: + def custom_serializer(self) -> list[Any]: if self.values is None: return [] return list(self.values) @@ -279,7 +279,7 @@ class Cartesian2Value(BaseCZMLObject): """ - values: Union[None, List[Any]] = Field(default=None) + values: None | list[Any] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -313,7 +313,7 @@ class CartographicRadiansValue(BaseCZMLObject): """ - values: Union[None, List[Any]] = Field(default=None) + values: None | list[Any] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -347,7 +347,7 @@ class CartographicDegreesValue(BaseCZMLObject): """ - values: Union[None, List[Any]] = Field(default=None) + values: None | list[Any] = Field(default=None) @model_validator(mode="after") def _check_values(self) -> Self: @@ -364,7 +364,7 @@ def _check_values(self) -> Self: return self @model_serializer - def custom_serializer(self) -> List[Any]: + def custom_serializer(self) -> list[Any]: if self.values is None: return [] return self.values @@ -387,7 +387,7 @@ class CartographicRadiansListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in radians and Height is in meters.""" - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -407,7 +407,7 @@ class CartographicDegreesListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in degrees and Height is in meters.""" - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -430,7 +430,7 @@ class DistanceDisplayConditionValue(BaseCZMLObject): where Time is an ISO 8601 date and time string or seconds since epoch. """ - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -454,7 +454,7 @@ class NearFarScalarValue(BaseCZMLObject): FarDistance, FarValue, ...], where Time is an ISO 8601 date and time string or seconds since epoch. """ - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -476,8 +476,8 @@ def custom_serializer(self): class TimeInterval(BaseCZMLObject): """A time interval, specified in ISO8601 interval format.""" - start: Union[str, dt.datetime] = Field(default="0001-01-01T00:00:00Z") - end: Union[str, dt.datetime] = Field(default="9999-12-31T23:59:59Z") + start: str | dt.datetime = Field(default="0001-01-01T00:00:00Z") + end: str | dt.datetime = Field(default="9999-12-31T23:59:59Z") @field_validator("start", "end") @classmethod @@ -492,12 +492,12 @@ def custom_serializer(self) -> str: class IntervalValue(BaseCZMLObject): """Value over some interval.""" - start: Union[str, dt.datetime] - end: Union[str, dt.datetime] - value: Union[Any] = Field(default=None) + start: str | dt.datetime + end: str | dt.datetime + value: Any = Field(default=None) @model_serializer - def custom_serializer(self) -> Dict[str, Any]: + def custom_serializer(self) -> dict[str, Any]: obj_dict = { "interval": TimeInterval(start=self.start, end=self.end).model_dump( exclude_none=True @@ -521,10 +521,10 @@ def custom_serializer(self) -> Dict[str, Any]: class Sequence(BaseCZMLObject): """Sequence, list, array of objects.""" - values: List[Any] + values: list[Any] @model_serializer - def custom_serializer(self) -> List[Any]: + def custom_serializer(self) -> list[Any]: return list(self.values) @@ -538,7 +538,7 @@ class UnitQuaternionValue(BaseCZMLObject): """ - values: Union[List[float], List[int]] + values: list[float] | list[int] @model_validator(mode="after") def _check_values(self) -> Self: @@ -557,7 +557,7 @@ def custom_serializer(self): class EpochValue(BaseCZMLObject): """A value representing a time epoch.""" - value: Union[str, dt.datetime] + value: str | dt.datetime @model_serializer def custom_serializer(self): @@ -567,7 +567,7 @@ def custom_serializer(self): class NumberValue(BaseCZMLObject): """A single number, or a list of number pairs signifying the time and representative value.""" - values: Union[int, float, List[float], List[int]] + values: int | float | list[float] | list[int] @model_serializer def custom_serializer(self): From 1b4ead727038840015f8dda7fb208dc46f55fad6 Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Wed, 27 Nov 2024 19:26:18 +0200 Subject: [PATCH 19/20] Drop Python 3.9 support --- .github/workflows/workflow.yml | 2 +- pyproject.toml | 7 ++----- src/czml3/common.py | 2 -- src/czml3/core.py | 2 -- src/czml3/types.py | 10 +++++----- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 80aba49..66c4247 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/pyproject.toml b/pyproject.toml index 3acf427..e00e138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,11 +13,10 @@ select = [ [tool.tox] legacy_tox_ini = """ [tox] - envlist = quality, test, pypy, pypy3, py{39,310,311,312,313} + envlist = quality, test, pypy, pypy3, py{310,311,312,313} [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311, quality, test, pypy, pypy3 3.12: py312 @@ -27,7 +26,6 @@ legacy_tox_ini = """ basepython = pypy: {env:PYTHON:pypy} pypy3: {env:PYTHON:pypy3} - py39: {env:PYTHON:python3.9} py310: {env:PYTHON:python3.10} py311: {env:PYTHON:python3.11} py312: {env:PYTHON:python3.12} @@ -77,7 +75,7 @@ authors = [ ] description = "Python 3 library to write CZML" readme = "README.rst" -requires-python = ">=3.9" +requires-python = ">=3.10" keywords = ["czml", "cesium", "orbits"] license = {text = "MIT"} classifiers = [ @@ -87,7 +85,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/czml3/common.py b/src/czml3/common.py index f038b57..0a3c6c9 100644 --- a/src/czml3/common.py +++ b/src/czml3/common.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import datetime as dt from pydantic import BaseModel, field_validator diff --git a/src/czml3/core.py b/src/czml3/core.py index 8c72b52..b42ac7f 100644 --- a/src/czml3/core.py +++ b/src/czml3/core.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any from uuid import uuid4 diff --git a/src/czml3/types.py b/src/czml3/types.py index 876ea29..add5d60 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -26,14 +26,14 @@ def get_color(color): """Determines if the input is a valid color""" if color is None or ( isinstance(color, list) - and all(issubclass(type(v), (int, float)) for v in color) + and all(issubclass(type(v), int | float) for v in color) and len(color) == 4 and (all(0 <= v <= 255 for v in color) or all(0 <= v <= 1 for v in color)) ): return color elif ( isinstance(color, list) - and all(issubclass(type(v), (int, float)) for v in color) + and all(issubclass(type(v), int | float) for v in color) and len(color) == 3 and all(0 <= v <= 255 for v in color) ): @@ -41,14 +41,14 @@ def get_color(color): # rgbf or rgbaf # if ( # isinstance(color, list) - # and all(issubclass(type(v), (int, float)) for v in color) + # and all(issubclass(type(v), int | float) for v in color) # and (3 <= len(color) <= 4) # and not all(0 <= v <= 1 for v in color) # ): # raise TypeError("RGBF or RGBAF values must be between 0 and 1") elif ( isinstance(color, list) - and all(issubclass(type(v), (int, float)) for v in color) + and all(issubclass(type(v), int | float) for v in color) and len(color) == 3 and all(0 <= v <= 1 for v in color) ): @@ -571,6 +571,6 @@ class NumberValue(BaseCZMLObject): @model_serializer def custom_serializer(self): - if isinstance(self.values, (int, float)): + if isinstance(self.values, int | float): return {"number": self.values} return {"number": list(self.values)} From 790c4bbaafdf01b95e6e081671ae867f86fba22b Mon Sep 17 00:00:00 2001 From: Daniel Stoops Date: Thu, 28 Nov 2024 09:05:02 +0200 Subject: [PATCH 20/20] Update readme --- README.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 5063382..c7b95d4 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,7 @@ or conda:: $ conda install czml3 --channel conda-forge -czml3 requires Python >= 3.8. +czml3 requires Python >= 3.10. Examples ======== @@ -79,7 +79,7 @@ all objects show as nice CZML (JSON):: "name": "AGI" } >>> packet0.dumps() - '{"id": "Facility/AGI", "name": "AGI"}' + '{"id":"Facility/AGI","name":"AGI"}' And there are more complex examples available:: @@ -93,9 +93,7 @@ And there are more complex examples available:: "clock": { "interval": "2012-03-15T10:00:00Z/2012-03-16T10:00:00Z", "currentTime": "2012-03-15T10:00:00Z", - "multiplier": 60, - "range": "LOOP_STOP", - "step": "SYSTEM_CLOCK_MULTIPLIER" + "multiplier": 60 } }, ...