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
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 = {}
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)
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
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
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)
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)
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)
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)
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
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
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() }
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
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
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
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
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
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
def test_DomainModel_persist_raises_error_if_no_persisters_added(): m = DomainModel() with pytest.raises(NoPersistenceModelAttachedError): m.persist()
"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,