diff --git a/src/pyfdl/common.py b/src/pyfdl/common.py index c9a15ef..c591bd8 100644 --- a/src/pyfdl/common.py +++ b/src/pyfdl/common.py @@ -241,7 +241,9 @@ def add(self, item: Any): self._data[item_id] = item else: - raise FDLError(f"Item must have a valid identifier (\"{self._cls.id_attribute}\"), not None or empty string") + raise FDLError( + f"Item must have a valid identifier (\"{self._cls.id_attribute}\"), not None or empty string" + ) def get(self, item_id: str) -> Union[Any, None]: """Get an item in the collection @@ -278,6 +280,9 @@ def _get_item_id(self, item: Any) -> str: """ return getattr(item, self._cls.id_attribute) + def __bool__(self): + return bool(self._data) + def __len__(self): return len(self._data) @@ -352,6 +357,7 @@ def copy(self) -> 'Dimensions': return Dimensions(width=self.width, height=self.height, dtype=self.dtype) def to_dict(self) -> dict: + # TODO: do we round before casting to int? return {'width': self.dtype(self.width), 'height': self.dtype(self.height)} def __iter__(self): @@ -421,6 +427,7 @@ def __init__(self, even: str = None, mode: str = None): super().__init__() self.even = even self.mode = mode + self.rounding_strategy = None @property def even(self): @@ -484,7 +491,13 @@ def round_dimensions( width = mode_map[self.mode](width / adjust) * adjust height = mode_map[self.mode](height / adjust) * adjust - return type(dimensions)(width=width, height=height) + return Dimensions(width=width, height=height, dtype=dimensions.dtype) + + def __eq__(self, other): + if isinstance(other, dict): + return self.even == other.get('even') and self.mode == other.get('mode') + + return self.even == other.even and self.mode == other.mode def __repr__(self): return f'{self.__class__.__name__}(even="{self.even}", mode="{self.mode}")' diff --git a/tests/conftest.py b/tests/conftest.py index 35ffe6f..980124a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pyfdl -@pytest.fixture +@pytest.fixture(scope="function") def base_subclass(): class BaseSubclass(pyfdl.Base): # Holds a list of known attributes @@ -39,8 +39,7 @@ def __init__( self.round = round_ # Make sure we have a rounding strategy - if pyfdl.Base.rounding_strategy is None: - pyfdl.Base.set_rounding_strategy() + self.set_rounding_strategy(pyfdl.DEFAULT_ROUNDING_STRATEGY) return BaseSubclass @@ -223,7 +222,7 @@ def sample_context() -> dict: return ctx -@pytest.fixture() +@pytest.fixture def sample_canvas_template() -> dict: canvas_template = { "label": "VFX Pull", @@ -240,7 +239,7 @@ def sample_canvas_template() -> dict: return canvas_template -@pytest.fixture() +@pytest.fixture def sample_canvas_template_kwargs() -> dict: canvas_template = { "label": "VFX Pull", diff --git a/tests/test_common.py b/tests/test_common.py index 123fc9f..05ddc57 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -72,111 +72,149 @@ def test_base_set_rounding_strategy(base_subclass, sample_rounding_strategy_obj) obj.set_rounding_strategy(rules=override) assert obj.rounding_strategy.to_dict() == override + # Reset to default to not mess up other tests + obj.set_rounding_strategy(pyfdl.DEFAULT_ROUNDING_STRATEGY) + def test_base_generate_uuid(base_subclass): assert isinstance(base_subclass.generate_uuid(), str) -def test_dimensions_point_from_dict(sample_point): - point1 = pyfdl.Point.from_dict(sample_point) - assert isinstance(point1, pyfdl.Point) - assert point1.check_required() == [] - assert point1.to_dict() == sample_point +def test_typed_collection_ids(sample_framing_intent_obj): + collection = pyfdl.TypedCollection(pyfdl.FramingIntent) + assert collection.ids == [] + collection.add(sample_framing_intent_obj) + assert collection.ids == [sample_framing_intent_obj.id] -def test_dimensions_point_from_kwargs(sample_point): - point2 = pyfdl.Point(**sample_point) - assert isinstance(point2, pyfdl.Point) - assert point2.check_required() == [] - assert point2.to_dict() == sample_point - point3 = pyfdl.Point(x=16, y=9) - assert isinstance(point3, pyfdl.Point) - assert point3.check_required() == [] - assert point3.x == 16 - assert point3.y == 9 +def test_typed_collection_add(sample_framing_intent_obj): + collection = pyfdl.TypedCollection(pyfdl.FramingIntent) + framing_intent = sample_framing_intent_obj + collection.add(framing_intent) + assert len(collection) == 1 - with pytest.raises(TypeError) as err: - pyfdl.Point() + # Add something of wrong type + with pytest.raises(TypeError): + collection.add(pyfdl.Point(x=10, y=10)) - assert "missing 2 required positional arguments: 'x' and 'y'" in str(err.value) + # Add duplicate "id" + with pytest.raises(pyfdl.FDLError) as err: + collection.add(framing_intent) + assert "already exists." in str(err) -def test_rounding_strategy_validation(): - rs = pyfdl.RoundStrategy() + # Add something with "id_attribute" not set + framing_intent_1 = pyfdl.FramingIntent() with pytest.raises(pyfdl.FDLError) as err: - rs.even = 'wrong' + collection.add(framing_intent_1) - assert '"wrong" is not a valid option for "even".' in str(err.value) + assert "Item must have a valid identifier" in str(err) - with pytest.raises(pyfdl.FDLError) as err: - rs.mode = 'wrong' - assert '"wrong" is not a valid option for "mode".' in str(err.value) +def test_typed_collection_get(sample_framing_intent_obj): + collection = pyfdl.TypedCollection(pyfdl.FramingIntent) + framing_intent = sample_framing_intent_obj + collection.add(framing_intent) + assert collection.get(framing_intent.id) == framing_intent + assert collection.get("not_present") is None -def test_rounding_strategy_default_values(): - rs = pyfdl.RoundStrategy() - assert rs.check_required() == ['even', 'mode'] +def test_typed_collection_remove(sample_framing_intent_obj): + collection = pyfdl.TypedCollection(pyfdl.FramingIntent) + framing_intent = sample_framing_intent_obj + collection.add(framing_intent) + assert len(collection) == 1 + collection.remove(framing_intent.id) + assert len(collection) == 0 + assert bool(collection) is False - rs.apply_defaults() - assert rs.even == 'even' - assert rs.mode == 'up' - assert rs.check_required() == [] +def test_typed_collection_to_list(sample_framing_intent_obj): + collection = pyfdl.TypedCollection(pyfdl.FramingIntent) + framing_intent = sample_framing_intent_obj + collection.add(framing_intent) -def test_rounding_strategy_from_dict(sample_rounding_strategy): - rs = pyfdl.RoundStrategy.from_dict(sample_rounding_strategy) - assert isinstance(rs, pyfdl.RoundStrategy) - assert rs.even == "even" - assert rs.mode == "up" + assert collection.to_list() == [framing_intent.to_dict()] -def test_typed_collection(sample_framing_intent, sample_framing_intent_kwargs): - td = pyfdl.TypedCollection(pyfdl.FramingIntent) - fi = pyfdl.FramingIntent.from_dict(sample_framing_intent) - td.add(fi) +def test_typed_collection_contents(sample_framing_intent_obj): + collection = pyfdl.TypedCollection(pyfdl.FramingIntent) + framing_intent = sample_framing_intent_obj + collection.add(framing_intent) + assert framing_intent in collection + assert framing_intent.id in collection + assert [_fi for _fi in collection] == [framing_intent] - assert td.to_list() == [fi.to_dict()] - assert td.ids == [f"{fi.id}"] - assert fi in td - assert fi.id in td - assert td.get(fi.id) == fi - assert [_fi for _fi in td] == [fi] +def test_dimensions_to_dict(): + dim_1 = pyfdl.Dimensions(width=1.1, height=2.2, dtype=int) + assert dim_1.to_dict() == {"width": 1, "height": 2} - td.remove(fi.id) - assert len(td) == 0 - assert bool(td) is False + dim_2 = pyfdl.Dimensions(width=1.1, height=2.2, dtype=float) + assert dim_2.to_dict() == {"width": 1.1, "height": 2.2} - with pytest.raises(TypeError) as err: - td.add(pyfdl.Point(x=10, y=10)) - assert "This container does not accept items of type:" in str(err.value) +def test_dimensions_scale_by(): + dim_1 = pyfdl.Dimensions(width=1.1, height=2.2, dtype=int) + dim_1.scale_by(2) + assert (dim_1.width, dim_1.height) == (2, 4) - # Test missing id - fi1 = pyfdl.FramingIntent() - with pytest.raises(pyfdl.FDLError) as err: - td.add(fi1) + dim_2 = pyfdl.Dimensions(width=1.1, height=2.2, dtype=float) + dim_2.scale_by(2) + assert (dim_2.width, dim_2.height) == (2.2, 4.4) - assert f"Item must have a valid identifier (\"id\")" in str(err.value) + # Check if scaling follows rounding rules + dim_3 = pyfdl.Dimensions(width=1.1, height=2.2, dtype=int) + dim_3.rounding_strategy = pyfdl.RoundStrategy(**{"even": "whole", "mode": "up"}) + dim_3.scale_by(2) + assert (dim_3.width, dim_3.height) == (3, 5) - # Test duplicate id's - td.add(fi) - kwargs = sample_framing_intent_kwargs.copy() - kwargs['label'] = 'somethingelse' - fi2 = pyfdl.FramingIntent(**kwargs) - with pytest.raises(pyfdl.FDLError) as err: - td.add(fi2) - assert f"FramingIntent.id (\"{fi.id}\") already exists." in str(err.value) +def test_dimensions_copy(): + dim_1 = pyfdl.Dimensions(width=1.1, height=2.2, dtype=int) + dim_2 = dim_1.copy() + assert dim_1 == dim_2 + assert dim_1.dtype == dim_2.dtype - # Test object with alternative id_attribute - td1 = pyfdl.TypedCollection(pyfdl.Context) - ctx1 = pyfdl.Context(label='context1') - td1.add(ctx1) +def test_rounding_strategy_validation(): + rs = pyfdl.RoundStrategy() with pytest.raises(pyfdl.FDLError) as err: - td1.add(ctx1) + rs.even = 'wrong' + + assert '"wrong" is not a valid option for "even".' in str(err.value) + + with pytest.raises(pyfdl.FDLError) as err: + rs.mode = 'wrong' + + assert '"wrong" is not a valid option for "mode".' in str(err.value) + - assert f"Context.label (\"{ctx1.label}\") already exists." in str(err) +@pytest.mark.parametrize( + 'rules,dimensions,dtype,expected', + [ + ({"even": "even", "mode": "up"}, {"width": 19, "height": 79}, int, (20, 80)), + ({"even": "even", "mode": "up"}, {"width": 19, "height": 79}, float, (20, 80)), + ({"even": "even", "mode": "down"}, {"width": 19, "height": 79}, int, (18, 78)), + ({"even": "even", "mode": "down"}, {"width": 19, "height": 79}, float, (18, 78)), + ({"even": "even", "mode": "round"}, {"width": 19, "height": 79}, int, (20, 80)), + ({"even": "even", "mode": "round"}, {"width": 19, "height": 79}, float, (20, 80)), + ({"even": "even", "mode": "round"}, {"width": 19.456, "height": 79.456}, int, (20, 80)), + ({"even": "even", "mode": "round"}, {"width": 19.456, "height": 79.456}, float, (20, 80)), + ({"even": "whole", "mode": "up"}, {"width": 19.5, "height": 79.5}, int, (20, 80)), + ({"even": "whole", "mode": "up"}, {"width": 19.5, "height": 79.5}, float, (20, 80)), + ({"even": "whole", "mode": "down"}, {"width": 19.5, "height": 79.5}, int, (19, 79)), + ({"even": "whole", "mode": "down"}, {"width": 19.5, "height": 79.5}, float, (19, 79)), + ({"even": "whole", "mode": "round"}, {"width": 19.5, "height": 79.5}, int, (20, 80)), + ({"even": "whole", "mode": "round"}, {"width": 19.5, "height": 79.5}, float, (20, 80)), + ({"even": "whole", "mode": "round"}, {"width": 19.456, "height": 79.456}, int, (19, 79)), + ({"even": "whole", "mode": "round"}, {"width": 19.456, "height": 79.456}, float, (19, 79)) + ] +) +def test_rounding_strategy_rounding(rules, dimensions, dtype, expected): + rnd = pyfdl.RoundStrategy.from_dict(rules) + dim = pyfdl.Dimensions(**dimensions, dtype=dtype) + result = rnd.round_dimensions(dim) + + assert (result.width, result.height) == expected