diff --git a/ereuse_devicehub/resources/device/sync.py b/ereuse_devicehub/resources/device/sync.py index 454e05fa..6d2ca495 100644 --- a/ereuse_devicehub/resources/device/sync.py +++ b/ereuse_devicehub/resources/device/sync.py @@ -13,10 +13,14 @@ from teal.marshmallow import ValidationError from ereuse_devicehub.db import db from ereuse_devicehub.resources.action.models import Remove -from ereuse_devicehub.resources.device.models import Component, Computer, Device, DataStorage +from ereuse_devicehub.resources.device.models import ( + Component, + Computer, + DataStorage, + Device, +) from ereuse_devicehub.resources.tag.model import Tag - DEVICES_ALLOW_DUPLICITY = [ 'RamModule', 'Display', @@ -26,12 +30,13 @@ DEVICES_ALLOW_DUPLICITY = [ 'GraphicCard', ] + class Sync: """Synchronizes the device and components with the database.""" - def run(self, - device: Device, - components: Iterable[Component] or None) -> (Device, OrderedSet): + def run( + self, device: Device, components: Iterable[Component] or None + ) -> (Device, OrderedSet): """Synchronizes the device and components with the database. Identifies if the device and components exist in the database @@ -72,9 +77,9 @@ class Sync: blacklist = set() # type: Set[int] not_new_components = set() for component in components: - db_component, is_new = self.execute_register_component(component, - blacklist, - parent=db_device) + db_component, is_new = self.execute_register_component( + component, blacklist, parent=db_device + ) db_components.add(db_component) if not is_new: not_new_components.add(db_component) @@ -83,10 +88,9 @@ class Sync: db_device.components = db_components return db_device, actions - def execute_register_component(self, - component: Component, - blacklist: Set[int], - parent: Computer): + def execute_register_component( + self, component: Component, blacklist: Set[int], parent: Computer + ): """Synchronizes one component to the DB. This method is a specialization of :meth:`.execute_register` @@ -118,9 +122,12 @@ class Sync: # if not, then continue with the traditional behaviour try: if component.hid: - db_component = Device.query.filter_by(hid=component.hid, owner_id=g.user.id).one() - assert isinstance(db_component, Device), \ - '{} must be a component'.format(db_component) + db_component = Device.query.filter_by( + hid=component.hid, owner_id=g.user.id + ).one() + assert isinstance( + db_component, Device + ), '{} must be a component'.format(db_component) else: # Is there a component similar to ours? db_component = component.similar_one(parent, blacklist) @@ -166,15 +173,31 @@ class Sync: :return: The synced device from the db with the tags linked. """ assert inspect(device).transient, 'Device cannot be already synced from DB' - assert all(inspect(tag).transient for tag in device.tags), 'Tags cannot be synced from DB' + assert all( + inspect(tag).transient for tag in device.tags + ), 'Tags cannot be synced from DB' db_device = None - if device.hid: + if isinstance(db_device, Computer): + if db_device.uuid: + db_device = Computer.query.filter_by( + uuid=device.uuid, owner_id=g.user.id, active=True + ).one() + elif device.hid: + with suppress(ResourceNotFound): + db_device = Device.query.filter_by( + hid=device.hid, owner_id=g.user.id, active=True + ).one() + elif device.hid: with suppress(ResourceNotFound): - db_device = Device.query.filter_by(hid=device.hid, owner_id=g.user.id, active=True).one() + db_device = Device.query.filter_by( + hid=device.hid, owner_id=g.user.id, active=True + ).one() if db_device and db_device.allocated: raise ResourceNotFound('device is actually allocated {}'.format(device)) try: - tags = {Tag.from_an_id(tag.id).one() for tag in device.tags} # type: Set[Tag] + tags = { + Tag.from_an_id(tag.id).one() for tag in device.tags + } # type: Set[Tag] except ResourceNotFound: raise ResourceNotFound('tag you are linking to device {}'.format(device)) linked_tags = {tag for tag in tags if tag.device_id} # type: Set[Tag] @@ -182,16 +205,22 @@ class Sync: sample_tag = next(iter(linked_tags)) for tag in linked_tags: if tag.device_id != sample_tag.device_id: - raise MismatchBetweenTags(tag, sample_tag) # Tags linked to different devices + raise MismatchBetweenTags( + tag, sample_tag + ) # Tags linked to different devices if db_device: # Device from hid - if sample_tag.device_id != db_device.id: # Device from hid != device from tags + if ( + sample_tag.device_id != db_device.id + ): # Device from hid != device from tags raise MismatchBetweenTagsAndHid(db_device.id, db_device.hid) else: # There was no device from hid if sample_tag.device.physical_properties != device.physical_properties: # Incoming physical props of device != props from tag's device # which means that the devices are not the same - raise MismatchBetweenProperties(sample_tag.device.physical_properties, - device.physical_properties) + raise MismatchBetweenProperties( + sample_tag.device.physical_properties, + device.physical_properties, + ) db_device = sample_tag.device if db_device: # Device from hid or tags self.merge(device, db_device) @@ -199,17 +228,21 @@ class Sync: device.tags.clear() # We don't want to add the transient dummy tags db.session.add(device) db_device = device - db_device.tags |= tags # Union of tags the device had plus the (potentially) new ones + db_device.tags |= ( + tags # Union of tags the device had plus the (potentially) new ones + ) try: db.session.flush() except IntegrityError as e: # Manage 'one tag per organization' unique constraint if 'One tag per organization' in e.args[0]: # todo test for this - id = int(e.args[0][135:e.args[0].index(',', 135)]) - raise ValidationError('The device is already linked to tag {} ' - 'from the same organization.'.format(id), - field_names=['device.tags']) + id = int(e.args[0][135 : e.args[0].index(',', 135)]) + raise ValidationError( + 'The device is already linked to tag {} ' + 'from the same organization.'.format(id), + field_names=['device.tags'], + ) else: raise assert db_device is not None @@ -222,15 +255,14 @@ class Sync: This method mutates db_device. """ if db_device.owner_id != g.user.id: - return + return for field_name, value in device.physical_properties.items(): if value is not None: setattr(db_device, field_name, value) @staticmethod - def add_remove(device: Computer, - components: Set[Component]) -> OrderedSet: + def add_remove(device: Computer, components: Set[Component]) -> OrderedSet: """Generates the Add and Remove actions (but doesn't add them to session). @@ -251,9 +283,13 @@ class Sync: if adding: # For the components we are adding, let's remove them from their old parents def g_parent(component: Component) -> Device: - return component.parent or Device(id=0) # Computer with id 0 is our Identity + return component.parent or Device( + id=0 + ) # Computer with id 0 is our Identity - for parent, _components in groupby(sorted(adding, key=g_parent), key=g_parent): + for parent, _components in groupby( + sorted(adding, key=g_parent), key=g_parent + ): set_components = OrderedSet(_components) check_owners = (x.owner_id == g.user.id for x in set_components) # Is not Computer Identity and all components have the correct owner @@ -263,21 +299,18 @@ class Sync: class MismatchBetweenTags(ValidationError): - def __init__(self, - tag: Tag, - other_tag: Tag, - field_names={'device.tags'}): - message = '{!r} and {!r} are linked to different devices.'.format(tag, other_tag) + def __init__(self, tag: Tag, other_tag: Tag, field_names={'device.tags'}): + message = '{!r} and {!r} are linked to different devices.'.format( + tag, other_tag + ) super().__init__(message, field_names) class MismatchBetweenTagsAndHid(ValidationError): - def __init__(self, - device_id: int, - hid: str, - field_names={'device.hid'}): - message = 'Tags are linked to device {} but hid refers to device {}.'.format(device_id, - hid) + def __init__(self, device_id: int, hid: str, field_names={'device.hid'}): + message = 'Tags are linked to device {} but hid refers to device {}.'.format( + device_id, hid + ) super().__init__(message, field_names) @@ -285,6 +318,8 @@ class MismatchBetweenProperties(ValidationError): def __init__(self, props1, props2, field_names={'device'}): message = 'The device from the tag and the passed-in differ the following way:' message += '\n'.join( - difflib.ndiff(yaml.dump(props1).splitlines(), yaml.dump(props2).splitlines()) + difflib.ndiff( + yaml.dump(props1).splitlines(), yaml.dump(props2).splitlines() + ) ) super().__init__(message, field_names)