class Enumerations(epyqlib.treenode.TreeNode): name = attr.ib( default='New Enumerations Group', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) children = attr.ib( default=attr.Factory(list), cmp=False, repr=False, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=( # TODO: would be nice to self reference without a name marshmallow.fields.Nested('Enumeration'), marshmallow.fields.Nested('AccessLevels'), )), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return isinstance(node, tuple(self.addable_types().values())) def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return True
class ArrayGroupElement(epyqlib.treenode.TreeNode): name = attr.ib( default='New Array Group Element', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) uuid = epyqlib.attrsmodel.attr_uuid() original = attr.ib( default=None, metadata=graham.create_metadata( field=epyqlib.attrsmodel.Reference(), ), ) epyqlib.attrsmodel.attrib( attribute=original, no_column=True, ) def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return False can_delete = epyqlib.attrsmodel.childless_can_delete
class TableRepeatingBlockReference(epyqlib.treenode.TreeNode): name = attr.ib( default="Table Repeating Block Reference", metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) # offset = attr.ib( # default=2, # converter=int, # ) children = attr.ib( factory=list, metadata=graham.create_metadata( field=graham.fields.MixedList( fields=( marshmallow.fields.Nested( graham.schema( TableRepeatingBlockReferenceFunctionDataReference, ) ), ) ), ), ) original = epyqlib.attrsmodel.create_reference_attribute() uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() @classmethod def all_addable_types(cls): return epyqlib.attrsmodel.create_addable_types(()) @staticmethod def addable_types(): return {} def can_drop_on(self, node): return False def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return False # def check_offsets_and_length(self): # return self.original.check_block_offsets_and_length() remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop child_from = epyqlib.attrsmodel.default_child_from internal_move = epyqlib.attrsmodel.default_internal_move check = epyqlib.attrsmodel.check_just_children
class Test: field = attr.ib(metadata=graham.create_metadata( field=epcpm.canmodel.HexadecimalIntegerField(), ), ) none = attr.ib( default=None, metadata=graham.create_metadata( field=epcpm.canmodel.HexadecimalIntegerField( allow_none=True, ), ), )
class DataPointBitfield(epyqlib.treenode.TreeNode): parameter_uuid = create_parameter_uuid_attribute() children = attr.ib( factory=list, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=(marshmallow.fields.Nested( graham.schema(DataPointBitfieldMember)), )), ), ) # TODO: though this only really makes sense as one of the bitfield types type_uuid = epyqlib.attrsmodel.attr_uuid( default=None, allow_none=True, human_name="Type", data_display=epyqlib.attrsmodel.name_from_uuid, list_selection_root="sunspec types", ) block_offset = attr.ib( default=0, converter=int, metadata=graham.create_metadata(field=marshmallow.fields.Integer(), ), ) size = create_size_attribute() uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return isinstance( node, ( DataPointBitfieldMember, epyqlib.pm.parametermodel.Parameter, ), ) def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return True def child_from(self, node): if isinstance(node, epyqlib.pm.parametermodel.Parameter): self.parameter_uuid = node.uuid return None return node remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop internal_move = epyqlib.attrsmodel.default_internal_move check = epyqlib.attrsmodel.check_just_children
class FixedBlock(epyqlib.treenode.TreeNode): name = attr.ib( default="Fixed Block", metadata=graham.create_metadata(field=marshmallow.fields.String(), ), ) offset = attr.ib( default=2, converter=int, ) children = attr.ib( factory=list, metadata=graham.create_metadata(field=graham.fields.MixedList(fields=( marshmallow.fields.Nested(graham.schema(DataPoint)), marshmallow.fields.Nested(graham.schema(DataPointBitfield)), )), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() self.pyqt_signals.child_added_complete.connect(self.update) self.pyqt_signals.child_removed_complete.connect(self.update) def can_drop_on(self, node): return isinstance( node, ( epyqlib.pm.parametermodel.Parameter, DataPoint, DataPointBitfield, ), ) def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return isinstance(node, (DataPoint, DataPointBitfield)) def child_from(self, node): if isinstance(node, epyqlib.pm.parametermodel.Parameter): return DataPoint(parameter_uuid=node.uuid) return node def update(self): block_offset = 0 for pt in self.children: pt.block_offset = block_offset block_offset = block_offset + pt.size check_offsets_and_length = check_block_offsets_and_length remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop internal_move = epyqlib.attrsmodel.default_internal_move check = epyqlib.attrsmodel.check_just_children
class Parameter(epyqlib.treenode.TreeNode): name = attr.ib( default="New Parameter", converter=str, metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) value = decimal_attrib(default=None) user_default = decimal_attrib(default=None, load_only=True) factory_default = decimal_attrib(default=None, load_only=True) minimum = decimal_attrib(default=None, load_only=True) maximum = decimal_attrib(default=None, load_only=True) parameter_uuid = epyqlib.attrsmodel.attr_uuid( default=None, allow_none=True, ) epyqlib.attrsmodel.attrib( attribute=parameter_uuid, human_name="Parameter UUID", ) readable = attr.ib( default=None, metadata=graham.create_metadata( field=marshmallow.fields.Boolean(allow_none=True), ), ) writable = attr.ib( default=None, metadata=graham.create_metadata( field=marshmallow.fields.Boolean(allow_none=True), ), ) uuid = epyqlib.attrsmodel.attr_uuid(load_only=True) def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return False can_delete = epyqlib.attrsmodel.childless_can_delete remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop child_from = epyqlib.attrsmodel.default_child_from internal_move = epyqlib.attrsmodel.default_internal_move check = epyqlib.attrsmodel.check_just_children
class FunctionDataReference(epyqlib.treenode.TreeNode): name = attr.ib( default="Table Data Point Reference", metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) offset = attr.ib( default=2, converter=int, ) children = attr.ib( factory=list, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=()), ), ) original = attr.ib( default=None, metadata=graham.create_metadata( field=epyqlib.attrsmodel.Reference(allow_none=True), ), ) epyqlib.attrsmodel.attrib( attribute=original, no_column=True, ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() @classmethod def all_addable_types(cls): return epyqlib.attrsmodel.create_addable_types(()) @staticmethod def addable_types(): return {} def can_drop_on(self, node): return False def can_delete(self, node=None): return False # check_offsets_and_length = check_block_offsets_and_length remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop child_from = epyqlib.attrsmodel.default_child_from
class AccessLevels(epyqlib.treenode.TreeNode): name = attr.ib( default='New Access Levels', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) children = attr.ib( default=attr.Factory(list), repr=False, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=( marshmallow.fields.Nested(graham.schema(AccessLevel)), )), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def items(self): for child in self.children: yield (child.name, child.value) def values(self): for child in self.children: yield child.value def can_drop_on(self, node): return False def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return True def by_name(self, name): level, = ( level for level in self.children if level.name.casefold() == name.casefold() ) return level def default(self): return min(self.children, key=lambda x: x.value)
class TableGroupElement(epyqlib.treenode.TreeNode): name = attr.ib( default=None, convert=epyqlib.attrsmodel.to_str_or_none, metadata=graham.create_metadata( field=marshmallow.fields.String(allow_none=True) ), ) path = attr.ib( factory=tuple, ) epyqlib.attrsmodel.attrib( attribute=path, no_column=True, ) graham.attrib( attribute=path, field=graham.fields.Tuple(marshmallow.fields.UUID()), ) children = attr.ib( default=attr.Factory(list), cmp=False, repr=False, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=( marshmallow.fields.Nested('TableGroupElement'), marshmallow.fields.Nested(graham.schema(TableArrayElement)), )), ), ) uuid = epyqlib.attrsmodel.attr_uuid() original = attr.ib(default=None) epyqlib.attrsmodel.attrib( attribute=original, no_column=True, ) def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return False def can_delete(self, node=None): return False
class OverlayConfiguration: output_path = create_path_attribute() recipes = attr.ib(metadata=graham.create_metadata( field=marshmallow.fields.List( marshmallow.fields.Nested(graham.schema(OverlayRecipe))), ), ) reference_path = attr.ib( default=None, converter=epyqlib.attrsmodel.to_pathlib_or_none, ) path = attr.ib(default=None) @classmethod def load(cls, path): schema = graham.schema(cls) configuration_text = path.read_text(encoding="utf-8") configuration = schema.loads(configuration_text).data configuration.path = path if configuration.reference_path is None: configuration.reference_path = path.parent return configuration def recipe_output_path(self, recipe): return self.reference_path / self.output_path / recipe.output_path def recipe_base_pmvs_path(self, recipe): return self.reference_path / recipe.base_pmvs_path def recipe_overlay_pmvs_paths(self, recipe): return [ self.reference_path / path for path in recipe.overlay_pmvs_paths ] def raw(self, echo=lambda *args, **kwargs: None): input_modification_times = [] output_modification_times = [] input_modification_times.append(self.path.stat().st_mtime) for recipe in self.recipes: output_path = self.recipe_output_path(recipe=recipe) echo(f"Checking: {os.fspath(output_path)}") try: stat = output_path.stat() except FileNotFoundError: output_modification_time = -math.inf else: output_modification_time = stat.st_mtime output_modification_times.append(output_modification_time) overlay_pmvs_paths = self.recipe_overlay_pmvs_paths(recipe=recipe) for overlay_pmvs_path in overlay_pmvs_paths: input_modification_times.append( overlay_pmvs_path.stat().st_mtime, ) return min(output_modification_times) < max(input_modification_times)
def create_path_attribute(): return attr.ib( converter=pathlib.Path, metadata=graham.create_metadata( field=marshmallow.fields.String(), ), )
class TableEnumerationReference(epyqlib.treenode.TreeNode): name = attr.ib( default='New Enumeration Reference', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) uuid = epyqlib.attrsmodel.attr_uuid() enumeration_uuid = epyqlib.attrsmodel.attr_uuid( default=None, allow_none=True, human_name='Enumeration', data_display=epyqlib.attrsmodel.name_from_uuid, list_selection_root='enumerations', ) def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return False can_delete = epyqlib.attrsmodel.childless_can_delete def link(self, enumeration): self.enumeration_uuid = enumeration.uuid
class Enumeration(epyqlib.treenode.TreeNode): name = attr.ib( default='New Enumeration', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) children = attr.ib( default=attr.Factory(list), repr=False, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=( marshmallow.fields.Nested(graham.schema(Enumerator)), marshmallow.fields.Nested(graham.schema(SunSpecEnumerator)), )), ), ) # children = attr.ib( # default=attr.Factory(list), # metadata=graham.create_metadata( # field=marshmallow.fields.List( # marshmallow.fields.Nested(graham.schema(Enumerator)), # ), # ), # ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def items(self): for child in self.children: yield (child.name, child.value) def values(self): for child in self.children: yield child.value def can_drop_on(self, node): return isinstance(node, Enumerator) def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return True
def create_name_attribute(default=None): return attr.ib( default=default, converter=to_str_or_none, metadata=graham.create_metadata( field=marshmallow.fields.String(allow_none=True), ), )
def create_str_attribute(default=""): return attr.ib( default=default, converter=str, metadata=graham.create_metadata( field=marshmallow.fields.String(), ), )
def create_checkbox_attribute(default=False): return attr.ib( default=default, converter=epyqlib.attrsmodel.two_state_checkbox, metadata=graham.create_metadata( field=marshmallow.fields.Boolean(), ), )
def create_integer_attribute(default=0): return attr.ib( default=default, converter=int, metadata=graham.create_metadata( field=marshmallow.fields.Integer(), ), )
class OverlayRecipe: output_path = create_path_attribute() base_pmvs_path = create_path_attribute() overlay_pmvs_paths = attr.ib( converter=to_list_of_pathlib, metadata=graham.create_metadata(field=marshmallow.fields.List( marshmallow.fields.String()), ), )
class FixedBlock(epyqlib.treenode.TreeNode): name = attr.ib( default='Fixed Block', metadata=graham.create_metadata(field=marshmallow.fields.String(), ), ) offset = attr.ib( default=2, converter=int, ) children = attr.ib( factory=list, metadata=graham.create_metadata(field=graham.fields.MixedList( fields=(marshmallow.fields.Nested(graham.schema(DataPoint)), )), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return isinstance( node, ( epyqlib.pm.parametermodel.Parameter, DataPoint, ), ) def can_delete(self, node=None): return False def child_from(self, node): if isinstance(node, epyqlib.pm.parametermodel.Parameter): return DataPoint(parameter_uuid=node.uuid) return node check_offsets_and_length = check_block_offsets_and_length remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop internal_move = epyqlib.attrsmodel.default_internal_move check = epyqlib.attrsmodel.check_just_children
def create_limited_string_attribute(default, allowed, not_allowed_first): return attr.ib( default=default, converter=LimitedStringConverter.build( allowed=allowed, not_allowed_first=not_allowed_first, ), metadata=graham.create_metadata( field=marshmallow.fields.String(), ), )
class PassThrough(epyqlib.treenode.TreeNode): original = attr.ib(metadata=graham.create_metadata( field=epyqlib.attrsmodel.Reference(), ), ) value = attr.ib( default=None, converter=epyqlib.attrsmodel.to_decimal_or_none, ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__()
class Group(epyqlib.treenode.TreeNode): name = attr.ib( default='New Group', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) type_name = attr.ib( default=None, converter=epyqlib.attrsmodel.to_str_or_none, metadata=graham.create_metadata( field=marshmallow.fields.String(allow_none=True), ), ) children = attr.ib( default=attr.Factory(list), cmp=False, repr=False, metadata=graham.create_metadata( field=graham.fields.MixedList(fields=( # TODO: would be nice to self reference without a name marshmallow.fields.Nested('Group'), marshmallow.fields.Nested('Array'), marshmallow.fields.Nested('Table'), marshmallow.fields.Nested(graham.schema(Parameter)), )), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return isinstance(node, tuple(self.addable_types().values())) def can_delete(self, node=None): if node is None: return self.tree_parent.can_delete(node=self) return True
def create_reference_attribute(): attribute = attr.ib( default=None, metadata=graham.create_metadata( field=epyqlib.attrsmodel.Reference(allow_none=True), ), ) epyqlib.attrsmodel.attrib( attribute=attribute, no_column=True, ) return attribute
class C(object): x = attr.ib( metadata=graham.create_metadata( field=graham.fields.MixedList( fields=( marshmallow.fields.Nested(graham.schema(A)), ), exclude=( B, ), ) ) )
class AccessLevel(epyqlib.treenode.TreeNode): name = attr.ib( default='New Access Level', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) value = attr.ib( default=None, converter=epyqlib.attrsmodel.to_decimal_or_none, metadata=graham.create_metadata( field=marshmallow.fields.Integer(allow_none=True), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return False can_delete = epyqlib.attrsmodel.childless_can_delete
class Result(epyqlib.treenode.TreeNode): node = attr.ib(default=None) epyqlib.attrsmodel.attrib( attribute=node, no_column=True, ) severity = attr.ib( default=None, metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) epyqlib.attrsmodel.attrib( attribute=severity, data_display=epyqlib.attrsmodel.name_from_enumerator, ) message = epyqlib.attrsmodel.create_str_attribute() children = attr.ib( default=attr.Factory(list), ) uuid = epyqlib.attrsmodel.attr_uuid(no_column=True) def __attrs_post_init__(self): super().__init__() def can_delete(self, node=None): return False def check(self, models): return None def can_drop_on(self, node): return False all_addable_types = epyqlib.attrsmodel.empty_all_addable_types addable_types = epyqlib.attrsmodel.empty_addable_types remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop child_from = epyqlib.attrsmodel.default_child_from internal_move = epyqlib.attrsmodel.default_internal_move
class FunctionDataBitfieldMember(epyqlib.treenode.TreeNode): parameter_uuid = create_parameter_uuid_attribute() bit_offset = attr.ib( default=None, converter=epyqlib.attrsmodel.to_int_or_none, metadata=graham.create_metadata( field=marshmallow.fields.Integer(allow_none=True), ), ) bit_length = create_size_attribute(default=1) type_uuid = epyqlib.attrsmodel.attr_uuid( default=None, allow_none=True, human_name="Type", data_display=epyqlib.attrsmodel.name_from_uuid, list_selection_root="staticmodbus types", ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return isinstance(node, epyqlib.pm.parametermodel.Parameter) def child_from(self, node): self.parameter_uuid = node.uuid return None can_delete = epyqlib.attrsmodel.childless_can_delete all_addable_types = epyqlib.attrsmodel.empty_all_addable_types addable_types = epyqlib.attrsmodel.empty_addable_types remove_old_on_drop = epyqlib.attrsmodel.default_remove_old_on_drop internal_move = epyqlib.attrsmodel.default_internal_move check = epyqlib.attrsmodel.check_just_children
class SunSpecEnumerator(epyqlib.treenode.TreeNode): name = attr.ib( default='New Sunspec Enumerator', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) label = attr.ib( default='', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) description = attr.ib( default='', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) notes = attr.ib( default='', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) value = attr.ib( default=None, converter=epyqlib.attrsmodel.to_decimal_or_none, metadata=graham.create_metadata( field=marshmallow.fields.Integer(allow_none=True), ), ) type = attr.ib( default='', metadata=graham.create_metadata( field=marshmallow.fields.String(), ), ) uuid = epyqlib.attrsmodel.attr_uuid() def __attrs_post_init__(self): super().__init__() def can_drop_on(self, node): return False can_delete = epyqlib.attrsmodel.childless_can_delete
class Project: paths = attr.ib( default=attr.Factory(Models), metadata=graham.create_metadata( field=marshmallow.fields.Nested(graham.schema(Models)), ) ) filename = attr.ib(default=None) models = attr.ib(default=attr.Factory(Models)) filters = attr.ib( default=( ('Parameter Project', ['pmp']), ('All Files', ['*']) ) ) data_filters = attr.ib( default=( ('Dataset', ['json']), ('All Files', ['*']) ) ) def save(self, parent=None): if self.filename is None: project_path = epyqlib.utils.qt.file_dialog( filters=self.filters, parent=parent, save=True, caption='Save Project As', ) if project_path is None: raise ProjectSaveCanceled() self.filename = pathlib.Path(project_path) project_directory = self.filename.parents[0] paths = Models() for name, path in self.paths.items(): if path is not None: paths[name] = path else: new_path = epyqlib.utils.qt.file_dialog( filters=self.data_filters, parent=parent, save=True, caption='Save {} As'.format(name.title()), ) if new_path is None: raise ProjectSaveCanceled() paths[name] = ( pathlib.Path(new_path).relative_to(project_directory) ) self.paths = paths with open(self.filename, 'w', newline='\n') as f: s = graham.dumps(self, indent=4).data f.write(s) if not s.endswith('\n'): f.write('\n') for path, model in zip(paths.values(), self.models.values()): s = graham.dumps(model.root, indent=4).data with open(project_directory / path, 'w', newline='\n') as f: f.write(s) if not s.endswith('\n'): f.write('\n')