Skip to content

Commit

Permalink
Add TypedList class to control array objects (#4)
Browse files Browse the repository at this point in the history
Signed-off-by: apetrynet <[email protected]>
  • Loading branch information
apetrynet authored Nov 26, 2023
1 parent 855b20a commit 643da3a
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 34 deletions.
6 changes: 4 additions & 2 deletions src/pyfdl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
RoundStrategy,
FDL_SCHEMA_MAJOR,
FDL_SCHEMA_MINOR,
FDL_SCHEMA_VERSION
FDL_SCHEMA_VERSION,
TypedList
)
from .header import Header
from .framing_intent import FramingIntent
Expand Down Expand Up @@ -39,7 +40,8 @@
'load',
'loads',
'Point',
'RoundStrategy'
'RoundStrategy',
'TypedList'
]

__version__ = "0.1.0"
Expand Down
38 changes: 33 additions & 5 deletions src/pyfdl/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from abc import ABC, abstractmethod
from collections import UserList
from collections.abc import MutableSequence
from typing import Type

from pyfdl.errors import FDLError

Expand Down Expand Up @@ -63,15 +66,15 @@ def to_dict(self) -> dict:
# check if empty value should be omitted
if key not in self.required and not value:
# Keys with arrays as values should pass (for now?)
if not isinstance(value, list):
if not isinstance(value, TypedList):
continue

# Arrays (aka lists) contain other objects
if isinstance(value, list):
if isinstance(value, TypedList):
value = [item.to_dict() for item in value]

# This should cover all known objects
elif isinstance(value, Base):
elif isinstance(value, (Base, TypedList)):
value = value.to_dict()

data[key] = value
Expand All @@ -96,7 +99,8 @@ def from_dict(cls, raw: dict):

if key in cls.object_map:
if isinstance(value, list):
value = [cls.object_map[key].from_dict(item) for item in value]
_cls = cls.object_map[key]
value = TypedList(_cls, [_cls.from_dict(item) for item in value])

else:
value = cls.object_map[key].from_dict(value)
Expand All @@ -113,6 +117,30 @@ def __str__(self) -> str:
return str(self.to_dict())


class TypedList(UserList):
def __init__(self, cls: Type[Base], items: list = None):
super().__init__()
self._cls = cls
if items:
self.extend(items)

def append(self, item):
self.insert(self.__len__(), item)

def extend(self, other):
for item in other:
self.append(item)

def insert(self, i, item):
if not isinstance(item, self._cls):
raise TypeError(
f"This list does not accept items of type: \"{type(item)}\". "
f"Please provide items of type: \"{self._cls}\""
)

super().insert(i, item)


class DimensionsFloat(Base):
attributes = ['width', 'height']
required = ['width', 'height']
Expand Down Expand Up @@ -189,4 +217,4 @@ def mode(self, value):
self._mode = value

def __repr__(self):
return f"{self.__class__.__name__}(even={self.even}, mode={self.mode})"
return f'{self.__class__.__name__}(even="{self.even}", mode="{self.mode}")'
8 changes: 4 additions & 4 deletions src/pyfdl/canvas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pyfdl import Base, DimensionsInt, Point, DimensionsFloat, FramingDecision
from pyfdl import Base, DimensionsInt, Point, DimensionsFloat, FramingDecision, TypedList


class Canvas(Base):
Expand Down Expand Up @@ -37,7 +37,7 @@ def __init__(
photosite_dimensions: DimensionsInt = None,
physical_dimensions: DimensionsFloat = None,
anamorphic_squeeze: float = None,
framing_decisions: list[FramingDecision] = None
framing_decisions: TypedList[FramingDecision] = None
):
self.label = label
self.id = _id
Expand All @@ -48,7 +48,7 @@ def __init__(
self.photosite_dimensions = photosite_dimensions
self.physical_dimensions = physical_dimensions
self.anamorphic_squeeze = anamorphic_squeeze
self.framing_decisions = framing_decisions or []
self.framing_decisions = framing_decisions or TypedList(FramingDecision)

def __repr__(self):
return f"{self.__class__.__name__}(label={self.label}, id={self.id})"
return f'{self.__class__.__name__}(label="{self.label}", id="{self.id}")'
2 changes: 1 addition & 1 deletion src/pyfdl/canvas_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,4 @@ def preserve_from_source_canvas(self, value):
self._preserve_from_source_canvas = value

def __repr__(self):
return f"{self.__class__.__name__}(label={self.label}, id={self.id})"
return f'{self.__class__.__name__}(label="{self.label}", id="{self.id})"'
11 changes: 4 additions & 7 deletions src/pyfdl/context.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from pyfdl import Canvas
from pyfdl.base import Base
from pyfdl import Base, Canvas, TypedList


class Context(Base):
attributes = ['label', 'context_creator', 'canvases']
object_map = {'canvases': Canvas}

def __init__(self, label: str = None, context_creator: str = None, canvases: list = None):
def __init__(self, label: str = None, context_creator: str = None, canvases: TypedList = None):
self.label = label
self.context_creator = context_creator
self.canvases = canvases or []

# TODO verify appending canvases
self.canvases = canvases or TypedList(Canvas)

def __repr__(self):
return f"{self.__class__.__name__}(label={self.label})"
return f'{self.__class__.__name__}(label="{self.label}")'
6 changes: 3 additions & 3 deletions src/pyfdl/framing_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(

def __repr__(self):
return (
f"{self.__class__.__name__}("
f"label={self.label}, id={self.id}, framing_intent_id={self.framing_intent_id}"
f")"
f'{self.__class__.__name__}('
f'label="{self.label}", id="{self.id}", framing_intent_id="{self.framing_intent_id}"'
f')'
)
2 changes: 1 addition & 1 deletion src/pyfdl/framing_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ def __init__(
self.protection = protection

def __repr__(self):
return f"{self.__class__.__name__}(label={self.label}, id={self.id})"
return f'{self.__class__.__name__}(label="{self.label}", id="{self.id}")'
10 changes: 5 additions & 5 deletions src/pyfdl/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ def __init__(

def __repr__(self):
return (
f"{self.__class__.__name__}("
f"uuid={self.uuid}, "
f"version={self.version}, "
f"fdl_creator={self.fdl_creator}, "
f"default_framing_intent={self.default_framing_intent}"
f'{self.__class__.__name__}('
f'uuid="{self.uuid}", '
f'version={self.version}, '
f'fdl_creator="{self.fdl_creator}", '
f'default_framing_intent="{self.default_framing_intent}"'
f")"
)
13 changes: 7 additions & 6 deletions src/pyfdl/pyfdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
FramingIntent,
Context,
CanvasTemplate,
TypedList,
FDL_SCHEMA_MAJOR,
FDL_SCHEMA_MINOR,
FDL_SCHEMA_VERSION
Expand Down Expand Up @@ -41,17 +42,17 @@ def __init__(
version: dict = None,
fdl_creator: str = None,
default_framing_intent: str = None,
framing_intents: list[FramingIntent] = None,
contexts: list[Context] = None,
canvas_templates: list[CanvasTemplate] = None
framing_intents: TypedList[FramingIntent] = None,
contexts: TypedList[Context] = None,
canvas_templates: TypedList[CanvasTemplate] = None
):
self.uuid = _uuid
self.version = version
self.fdl_creator = fdl_creator
self.default_framing_intent = default_framing_intent
self.framing_intents = framing_intents or []
self.contexts = contexts or []
self.canvas_templates = canvas_templates or []
self.framing_intents = framing_intents or TypedList(FramingIntent)
self.contexts = contexts or TypedList(Context)
self.canvas_templates = canvas_templates or TypedList(CanvasTemplate)
self._schema = None

def validate(self):
Expand Down
41 changes: 41 additions & 0 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,44 @@ def test_rounding_strategy_default_values():
assert rs.even == 'even'
assert rs.mode == 'up'
assert rs.check_required() == []


def test_typed_init_list():
vl1 = pyfdl.TypedList(pyfdl.Point)
assert isinstance(vl1, pyfdl.TypedList)
assert vl1 == []

point = pyfdl.Point(x=10, y=10)
vl2 = pyfdl.TypedList(pyfdl.Point, [point])
assert isinstance(vl2, pyfdl.TypedList)
assert vl2[0] == point


def test_typed_list_raise():
vl = pyfdl.TypedList(pyfdl.Point)
with pytest.raises(TypeError):
vl.append('string')


def test_typed_list_append():
point = pyfdl.Point(x=10, y=10)
vl1 = pyfdl.TypedList(pyfdl.Point)
vl1.append(point)
assert vl1[0] == point


def test_typed_list_extend():
points = [pyfdl.Point(x=10, y=10)]
vl1 = pyfdl.TypedList(pyfdl.Point)
vl1.extend(points)
assert len(vl1) == 1
assert vl1[0] == points[0]


def test_typed_list_insert():
points = [pyfdl.Point(x=10, y=10), pyfdl.Point(x=30, y=30)]
expected_points = [pyfdl.Point(x=10, y=10), pyfdl.Point(x=20, y=20), pyfdl.Point(x=30, y=30)]
vl1 = pyfdl.TypedList(pyfdl.Point, points)
vl1.insert(1, pyfdl.Point(x=20, y=20))
assert len(vl1) == 3
assert repr(vl1) == repr(expected_points)

0 comments on commit 643da3a

Please sign in to comment.