Files
specklepy/tests/unit/test_base.py
T
Jedd Morgan d1b6755997 Removes all FE1 client functions (#380)
* Removes all FE1 client functions

* Removed usages of deprecated client functions

* removed trailing deprecated client function

* ruff

* Fixed last failing test
2025-02-18 15:32:14 +00:00

191 lines
4.9 KiB
Python

from contextlib import ExitStack as does_not_raise
from enum import Enum
from typing import Dict, List, Optional, Union
import pytest
from specklepy.api import operations
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.base import Base
from specklepy.objects.interfaces import IHasUnits
from specklepy.objects.models.units import Units
@pytest.mark.parametrize(
"invalid_prop_name",
[
"",
"@",
"@@wow",
"this.is.bad",
"super/bad",
],
)
def test_empty_prop_names(invalid_prop_name: str) -> None:
base = Base()
with pytest.raises(ValueError):
base[invalid_prop_name] = "🐛️"
class FakeModel(Base):
"""Just a test class type."""
class FakeSub(FakeModel):
"""Just a test class type."""
def test_new_type_registration() -> None:
"""Test if a new subclass is registered into the type register."""
assert Base.get_registered_type(FakeModel.speckle_type) == FakeModel
assert Base.get_registered_type(FakeSub.speckle_type) == FakeSub
assert Base.get_registered_type("🐺️") is None
def test_fake_base_serialization() -> None:
fake_model = FakeModel()
fake_model.foo = "bar"
serialized = operations.serialize(fake_model)
deserialized = operations.deserialize(serialized)
assert fake_model.get_id() == deserialized.get_id()
def test_duplicate_speckle_type_raises_error():
with pytest.raises(ValueError):
class NaughtyClass(Base, speckle_type="Base"):
"""This class has a speckle_type that is already taken."""
@pytest.mark.parametrize(
"forbidden_attribute_name, expectation",
[
("", pytest.raises(ValueError)),
("@", pytest.raises(ValueError)),
("@@", pytest.raises(ValueError)),
("im.cheeky", pytest.raises(ValueError)),
("im.cheeky", pytest.raises(ValueError)),
("imgood", does_not_raise()),
],
)
def test_attribute_name_validation(
forbidden_attribute_name: str,
expectation,
base: Base,
):
with expectation:
base[forbidden_attribute_name] = None
def test_speckle_type_cannot_be_set(base: Base) -> None:
assert base.speckle_type == "Base"
base.speckle_type = "unset" # type: ignore
assert base.speckle_type == "Base"
class FakeUnitBase(Base, IHasUnits, speckle_type="UnityBase"):
pass
def test_setting_units():
b = FakeUnitBase()
b.units = "foot"
assert b.units == "foot"
# with pytest.raises(SpeckleInvalidUnitException):
b.units = "big"
assert b.units == "big"
# invalid args are skipped
with pytest.raises(SpeckleException):
b.units = 7 # type: ignore
assert b.units == "big"
# None should be not be a valid arg
with pytest.raises(SpeckleException):
b.units = None # type: ignore
assert b.units == "big"
b.units = Units.none
assert b.units == "none"
b.units = Units.cm
assert b.units == Units.cm.value
def test_base_of_custom_speckle_type() -> None:
b1 = Base.of_type("BirdHouse", applicationId="Tweety's Crib")
assert b1.speckle_type == "BirdHouse"
assert b1.applicationId == "Tweety's Crib"
class DietaryRestrictions(Enum):
VEGAN = 1
GLUTEN_FREE = 2
NUT_FREE = 3
class FrozenYoghurt(Base):
"""Testing type checking"""
servings: int
flavours: List[str] # list item types won't be checked
customer: str
add_ons: Optional[Dict[str, float]] # dict item types won't be checked
price: float = 0.0
dietary: DietaryRestrictions
tag: Union[int, str]
def test_type_checking() -> None:
order = FrozenYoghurt()
order.servings = 2
order.price = "7" # type: ignore - it will get converted
order.customer = "izzy"
order.dietary = DietaryRestrictions.VEGAN
order.tag = "preorder"
order.tag = 4411
with pytest.raises(SpeckleException):
order.flavours = "not a list" # type: ignore
with pytest.raises(SpeckleException):
order.servings = "five" # type: ignore
with pytest.raises(SpeckleException):
order.add_ons = ["sprinkles"] # type: ignore
with pytest.raises(SpeckleException):
order.dietary = "no nuts plz" # type: ignore
with pytest.raises(SpeckleException):
order.tag = ["tag01", "tag02"] # type: ignore
order.add_ons = {"sprinkles": 0.2, "chocolate": 1.0}
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
assert order.price == 7.0
assert order.dietary == DietaryRestrictions.VEGAN
def test_cached_deserialization() -> None:
material = Base()
material.color = "blue"
material.opacity = 0.5
a = Base()
a.name = "a"
a["@material"] = material
b = Base()
b.name = "b"
b["@material"] = material
root = Base()
root.a = a
root.b = b
serialized = operations.serialize(root)
deserialized = operations.deserialize(serialized)
assert deserialized["a"]["@material"] is deserialized["b"]["@material"]