Ejemplo n.º 1
0
def test_DomainModel_get_persistence_model__returns_single_persister_if_one_present(
):
    m = DomainModel()
    p = DummyPersister()
    m.add_persistence_model(p)

    actual = m.get_persistence_model(object)
    assert actual == p
Ejemplo n.º 2
0
 def __init__(self, _redis_host='localhost', _redis_port=6379):
     self.event_store = EventStoreClient()
     self.consumers = Consumers('read-model', [self.get_entity,
                                               self.get_entities,
                                               self.get_mails,
                                               self.get_unbilled_orders,
                                               self.get_unshipped_orders,
                                               self.get_delivered_orders])
     self.domain_model = DomainModel(
         redis.StrictRedis(host=_redis_host, port=_redis_port, decode_responses=True)
     )
     self.subscriptions = {}
     self.locks = {}
Ejemplo n.º 3
0
def test_validate_domain_model_raises_error_if_not_correct_single_type():
    m = DomainModel()
    with pytest.raises(InvalidDomainModelSubclassError) as e:
        validate_domain_model(m,
                              instance_of=SubclassedDomainModel1,
                              extra_error_msg="wakka7")
    assert "wakka7" in str(e)
Ejemplo n.º 4
0
def test_validate_domain_model_does_not_autopopulated_if_disabled(mocker):
    m = DomainModel()
    spied_validate = mocker.spy(m, "validate")
    spied_autopopulate = mocker.spy(m, "autopopulate")
    validate_domain_model(m, autopopulate=False)
    assert spied_validate.call_count == 1
    assert spied_autopopulate.call_count == 0
Ejemplo n.º 5
0
def test_validate_domain_model_calls_validate_and_autopopulate_of_that_model(
        mocker):
    m = DomainModel()
    spied_validate = mocker.spy(m, "validate")
    spied_autopopulate = mocker.spy(m, "autopopulate")
    validate_domain_model(m)
    assert spied_validate.call_count == 1
    assert spied_autopopulate.call_count == 1
Ejemplo n.º 6
0
def test_validate_domain_model_raises_error_if_not_correct_tuple_of_types():
    m = DomainModel()
    with pytest.raises(InvalidDomainModelSubclassError) as e:
        validate_domain_model(
            m,
            instance_of=(SubclassedDomainModel1, SubclassedDomainModel2),
            extra_error_msg="wakka98",
        )
    assert "wakka98" in str(e)
Ejemplo n.º 7
0
def test_DomainModel_get_persistence_model__raises_error_if_persistence_model_type_not_present(
):
    m = DomainModel()
    p = DummyPersister()
    m.add_persistence_model(p)

    with pytest.raises(NoSpecifiedTypeOfPersistenceModelAttachedError):
        m.get_persistence_model(DomainModel)
Ejemplo n.º 8
0
def test_DomainModel_persist_calls_persisters(mocker):
    persister_1 = DummyPersister()
    persister_2 = DummyPersister()

    m = DomainModel()
    m.add_persistence_model(persister_1)
    m.add_persistence_model(persister_2)
    mocked_persist_1 = mocker.patch.object(persister_1, "persist")
    mocked_persist_2 = mocker.patch.object(persister_2, "persist")
    m.persist()

    mocked_persist_1.assert_called_once_with(m)
    mocked_persist_2.assert_called_once_with(m)
Ejemplo n.º 9
0
def test_DomainModel__hash__raises_error_if_attributes_not_specified():
    m = DomainModel()
    with pytest.raises(HashAttributesNotSpecifiedForClassError) as e:
        hash(m)
    assert "DomainModel" in str(e)
Ejemplo n.º 10
0
def test_DomainModel_persist_calls_autopopulate(mocker):
    m = DomainModel()
    m.add_persistence_model(DummyPersister())
    mocked_autopopulate = mocker.patch.object(m, "autopopulate")
    m.persist()
    assert mocked_autopopulate.call_count == 1
Ejemplo n.º 11
0
def test_DomainModel__persist__calls_persist_additional_models(mocker):
    m = DomainModel()
    m.add_persistence_model(DummyPersister())
    mocked_func = mocker.patch.object(m, "_persist_additional_models")
    m.persist()
    assert mocked_func.call_count == 1
Ejemplo n.º 12
0
class ReadModel(object):
    """
    Read Model class.
    """

    def __init__(self, _redis_host='localhost', _redis_port=6379):
        self.event_store = EventStoreClient()
        self.consumers = Consumers('read-model', [self.get_entity,
                                                  self.get_entities,
                                                  self.get_mails,
                                                  self.get_unbilled_orders,
                                                  self.get_unshipped_orders,
                                                  self.get_delivered_orders])
        self.domain_model = DomainModel(
            redis.StrictRedis(host=_redis_host, port=_redis_port, decode_responses=True)
        )
        self.subscriptions = {}
        self.locks = {}

    @staticmethod
    def _deduce_entities(_events):
        """
        Deduce entities from events.

        :param _events: A list with events.
        :return: A dict mapping entity ID -> entity data.
        """
        if not _events:
            return {}

        # find 'created' events
        result = {json.loads(e[1]['event_data'])['entity_id']: json.loads(e[1]['event_data'])
                  for e in filter(lambda x: x[1]['event_action'] == 'entity_created', _events)}

        # remove 'deleted' events
        deleted = {json.loads(e[1]['event_data'])['entity_id']: json.loads(e[1]['event_data'])
                   for e in filter(lambda x: x[1]['event_action'] == 'entity_deleted', _events)}

        for d_id, d_data in deleted.items():
            del result[d_id]

        # change 'updated' events
        updated = {json.loads(e[1]['event_data'])['entity_id']: json.loads(e[1]['event_data'])
                   for e in filter(lambda x: x[1]['event_action'] == 'entity_updated', _events)}

        for u_id, u_data in updated.items():
            result[u_id] = u_data

        return result

    def _track_entities(self, _name, _event):
        """
        Keep track of entity events.

        :param _name: The entity name.
        :param _event: The event data.
        """
        if not self.domain_model.exists(_name):
            return

        entity = json.loads(_event.event_data)

        if _event.event_action == 'entity_created':
            self.domain_model.create(_name, entity)

        if _event.event_action == 'entity_deleted':
            self.domain_model.delete(_name, entity)

        if _event.event_action == 'entity_updated':
            self.domain_model.update(_name, entity)

    def _query_entities(self, _name):
        """
        Query all entities of a given name.

        :param _name: The entity name.
        :return: A dict mapping entity ID -> entity.
        """
        entities = self.domain_model.retrieve(_name)
        if entities:
            return entities

        if _name not in self.locks:
            self.locks[_name] = threading.Lock()

        with self.locks[_name]:
            entities = self.domain_model.retrieve(_name)
            if entities:
                return entities

            # deduce entities
            events = self.event_store.get(_name)
            entities = self._deduce_entities(events)

            # cache entities
            for entity in entities.values():
                self.domain_model.create(_name, entity)

            # track entities
            tracking_handler = functools.partial(self._track_entities, _name)
            self.event_store.subscribe(_name, tracking_handler)
            self.subscriptions[_name] = tracking_handler

            return entities

    def _query_defined_entities(self, _name, _props):
        """
        Query entities with defined properities.

        :param _name: The entity name.
        :param _props: A dict mapping property name -> property value(s).
        :return: A dict mapping entity ID -> entity.
        """
        result = {}
        for entity_id, entity in self._query_entities(_name).items():
            for prop_name, prop_value in _props.items():
                if not isinstance(prop_value, list):
                    prop_value = [prop_value]
                if prop_name in entity and entity[prop_name] in prop_value:
                    result[entity_id] = entity

        return result

    def _unbilled_orders(self):
        """
        Query all unbilled orders, i.e. orders w/o corresponding billing.

        :return: a dict mapping entity ID -> entity.
        """
        orders = self._query_entities('order')
        billings = self._query_entities('billing')

        unbilled = orders.copy()
        for billing_id, billing in billings.items():
            order_ids_to_remove = list(filter(lambda x: x == billing['order_id'], orders))
            if not order_ids_to_remove:
                raise Exception(f'could not find order {billing["order_id"]} for billing {billing_id}')

            if order_ids_to_remove[0] not in unbilled:
                raise Exception(f'could not find order {order_ids_to_remove[0]}')

            del unbilled[order_ids_to_remove[0]]

        return unbilled

    def _unshipped_orders(self):
        """
        Query all unshipped orders, i.e. orders w/o corresponding shipping done.

        :return: a dict mapping entity ID -> entity.
        """
        orders = self._query_entities('order')
        shippings = self._query_entities('shipping')

        unshipped = orders.copy()
        for shipping_id, shippings in shippings.items():
            order_ids_to_remove = list(filter(lambda x: x == shippings['order_id'], orders))
            if not order_ids_to_remove:
                raise Exception(f'could not find order {shippings["order_id"]} for shipping {shipping_id}')

            if order_ids_to_remove[0] not in unshipped:
                raise Exception(f'could not find order {order_ids_to_remove[0]}')

            del unshipped[order_ids_to_remove[0]]

        return unshipped

    def _delivered_orders(self):
        """
        Query all delivered orders.

        :return: a dict mapping entity ID -> entity.
        """
        shippings = self._query_entities('shipping')

        return list(filter(lambda x: x['delivered'], shippings.values()))

    def start(self):
        logging.info('starting ...')
        self.consumers.start()
        self.consumers.wait()

    def stop(self):
        for name, handler in self.subscriptions.items():
            self.event_store.unsubscribe(name, handler)
        self.consumers.stop()
        logging.info('stopped.')

    def get_entity(self, _req):
        if 'name' not in _req:
            return {
                "error": "missing mandatory parameter 'name'"
            }

        if 'id' in _req:
            return {
                'result': self._query_entities(_req['name']).get(_req['id'])
            }

        elif 'props' in _req and isinstance(_req['props'], dict):
            result = list(self._query_defined_entities(_req['name'], _req['props']).values())
            if len(result) <= 1:
                return {
                    'result': result[0] if result else None
                }
            else:
                return {
                    'error': 'more than 1 result found'
                }
        else:
            return {
                'result': 'invalid parameters'
            }

    def get_entities(self, _req):
        if 'name' not in _req:
            return {
                "error": "missing mandatory parameter 'name'"
            }

        elif 'ids' in _req and isinstance(_req['ids'], list):
            return {
                'result': [self._query_entities(_req['name']).get(_id) for _id in _req['ids']]
            }

        elif 'props' in _req and isinstance(_req['props'], dict):
            return {
                'result': list(self._query_defined_entities(_req['name'], _req['props']).values())
            }

        else:
            return {
                'result': list(self._query_entities(_req['name']).values())
            }

    def get_mails(self, _req):
        return {
            'result': self.event_store.get('mail') or []
        }

    def get_unbilled_orders(self, _req):
        return {
            'result': self._unbilled_orders()
        }

    def get_unshipped_orders(self, _req):
        return {
            'result': self._unshipped_orders()
        }

    def get_delivered_orders(self, _req):
        return {
            'result': self._delivered_orders()
        }
Ejemplo n.º 13
0
def test_domainModelSave():
    foo = DomainModel()
    persister = MemoryPersister()
    foo.addPersister(persister)
    foo.bar = 1
    foo.persist()
    foo.bar = 2
    foo.persist()
    foo.bar = 3
    foo.persist()
    assert persister.savedStates.pop().bar == 3
    assert persister.savedStates.pop().bar == 2
    assert persister.savedStates.pop().bar == 1
Ejemplo n.º 14
0
def test_DomainModel_validate_hash_components_calls_autopopulate_by_default(
        mocker):
    m = DomainModel()
    spied_autopopulate = mocker.spy(m, "autopopulate")
    m.validate_hash_components()
    assert spied_autopopulate.call_count == 1
Ejemplo n.º 15
0
def test_DomainModel_validate_calls_validate_internals(mocker):
    m = DomainModel()
    spied_validate_internals = mocker.spy(m, "validate_internals")
    m.validate()
    assert spied_validate_internals.call_count == 1
Ejemplo n.º 16
0
def test_DomainModel_validate_calls_autopopulate_by_default(mocker):
    m = DomainModel()
    mocked_autopopulate = mocker.spy(m, "autopopulate")
    m.validate()
    assert mocked_autopopulate.call_count == 1
Ejemplo n.º 17
0
def test_validate_domain_model__still_autopopulates_even_if_not_validating(
        mocker):
    m = DomainModel()
    mocked_autopopulate = mocker.spy(m, "autopopulate")
    validate_domain_model(m, validate=False)
    assert mocked_autopopulate.call_count == 1
Ejemplo n.º 18
0
def test_validate_domain_model__does_not_validate_model_if_set(mocker):
    m = DomainModel()
    mocked_validate = mocker.spy(m, "validate")
    validate_domain_model(m, validate=False)
    assert mocked_validate.call_count == 0
Ejemplo n.º 19
0
def test_DomainModel_persist_raises_error_if_no_persisters_added():
    m = DomainModel()
    with pytest.raises(NoPersistenceModelAttachedError):
        m.persist()
Ejemplo n.º 20
0
     "uuid",
     "not a UUID",
 ),
 (
     BarcodedSbsLabware,
     "uuid",
     None,
     copy_dict_with_key_removed(GENERIC_BARCODED_SBS_LABWARE_KWARGS, "uuid"),
     ValidationCollectionEmptyValueError,
     "uuid",
     "null value",
 ),
 (
     BarcodedSbsLabware,
     "labware_definition",
     DomainModel(),
     copy_dict_with_key_removed(
         GENERIC_BARCODED_SBS_LABWARE_KWARGS, "labware_definition"
     ),
     InvalidDomainModelSubclassError,
     "labware_definition",
     "not a LabwareDefinition",
 ),
 (
     BarcodedSbsLabware,
     "labware_definition",
     None,
     copy_dict_with_key_removed(
         GENERIC_BARCODED_SBS_LABWARE_KWARGS, "labware_definition"
     ),
     ObjectIsNullError,