Skip to content
This repository has been archived by the owner on Dec 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #151 from vishwa2710/feat/number-and-epoch-types
Browse files Browse the repository at this point in the history
feat: number and epoch types, IntervalValue list parsing
  • Loading branch information
Stoops-ML authored Jul 17, 2024
2 parents 1360944 + 406cde3 commit 8c94a9c
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 2 deletions.
48 changes: 46 additions & 2 deletions src/czml3/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,12 @@ class IntervalValue(BaseCZMLObject):
def to_json(self):
obj_dict = {"interval": TimeInterval(start=self._start, end=self._end)}

try:
if isinstance(self._value, BaseCZMLObject):
obj_dict.update(**self._value.to_json())
except AttributeError:
elif isinstance(self._value, list):
for value in self._value:
obj_dict.update(**value.to_json())
else:
key = TYPE_MAPPING[type(self._value)]
obj_dict[key] = self._value

Expand Down Expand Up @@ -374,3 +377,44 @@ class UnitQuaternionValue(_TimeTaggedCoords):
"""

NUM_COORDS = 4


@attr.s(str=False, frozen=True, kw_only=True)
class EpochValue(BaseCZMLObject):
"""A value representing a time epoch."""

_value = attr.ib()

@_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.")

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

elif not isinstance(value, (int, float)):
raise ValueError("Values must be integers or floats.")

def to_json(self):
if isinstance(self.values, (int, float)):
return {"number": self.values}

return {"number": list(self.values)}
118 changes: 118 additions & 0 deletions tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import astropy.time
import pytest
from czml3.base import BaseCZMLObject
from czml3.types import (
Cartesian3Value,
CartographicDegreesListValue,
CartographicRadiansListValue,
DistanceDisplayConditionValue,
EpochValue,
FontValue,
IntervalValue,
NearFarScalarValue,
NumberValue,
ReferenceValue,
RgbafValue,
RgbaValue,
Expand Down Expand Up @@ -187,6 +191,120 @@ def test_bad_time_raises_error():
format_datetime_like("2019/01/01")


def test_interval_value():
start = "2019-01-01T12:00:00.000000Z"
end = "2019-09-02T21:59:59.000000Z"

# value is a boolean
assert (
str(IntervalValue(start=start, end=end, value=True))
== """{
"interval": "2019-01-01T12:00:00.000000Z/2019-09-02T21:59:59.000000Z",
"boolean": true
}"""
)

# 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(
start=start,
end=end,
value=[
EpochValue(value=start),
NumberValue(values=[1, 2, 3, 4]),
],
)
)
== """{
"interval": "2019-01-01T12:00:00.000000Z/2019-09-02T21:59:59.000000Z",
"epoch": "2019-01-01T12:00:00.000000Z",
"number": [
1,
2,
3,
4
]
}"""
)


def test_epoch_value():
epoch: str = "2019-01-01T12:00:00.000000Z"

assert (
str(EpochValue(value=epoch))
== """{
"epoch": "2019-01-01T12:00:00.000000Z"
}"""
)

assert (
str(EpochValue(value=dt.datetime(2019, 1, 1, 12)))
== """{
"epoch": "2019-01-01T12:00:00.000000Z"
}"""
)

with pytest.raises(expected_exception=ValueError):
str(EpochValue(value="test"))

with pytest.raises(
expected_exception=ValueError,
match="Epoch must be a string or a datetime object.",
):
EpochValue(value=1)


def test_numbers_value():
expected_result = """{
"number": [
1,
2,
3,
4
]
}"""
numbers = NumberValue(values=[1, 2, 3, 4])

assert str(numbers) == expected_result

expected_result = """{
"number": 1.0
}"""
numbers = NumberValue(values=1.0)

assert str(numbers) == expected_result

with pytest.raises(
expected_exception=ValueError, match="Values must be integers or floats."
):
NumberValue(values="test")

with pytest.raises(
expected_exception=ValueError, match="Values must be integers or floats."
):
NumberValue(values=[1, "test"])

with pytest.raises(
expected_exception=ValueError,
match="Values must be a list of number pairs signifying the time and representative value.",
):
NumberValue(values=[1, 2, 3, 4, 5])


@pytest.mark.xfail
def test_astropy_time_retains_input_format():
# It would be nice to recover the input format,
Expand Down

0 comments on commit 8c94a9c

Please sign in to comment.