From 3ab572af63e891a6dfafffeb6b3e8086204afc97 Mon Sep 17 00:00:00 2001 From: Christoph Blessing <33834216+cblessing24@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:52:48 +0100 Subject: [PATCH 1/2] Rename the state "idle" to "unshared" --- docs/entity_states.md | 14 +++++++------- link/adapters/controller.py | 6 +++--- link/adapters/present.py | 10 +++++----- link/domain/commands.py | 2 +- link/domain/events.py | 4 ++-- link/domain/link.py | 8 ++++---- link/domain/state.py | 12 ++++++------ link/infrastructure/link.py | 14 ++++++++------ link/infrastructure/mixin.py | 2 +- link/service/handlers.py | 12 ++++++------ tests/integration/test_services.py | 26 +++++++++++++------------- tests/integration/test_uow.py | 10 +++++----- tests/unit/entities/test_link.py | 2 +- tests/unit/entities/test_state.py | 8 ++++---- 14 files changed, 66 insertions(+), 64 deletions(-) diff --git a/docs/entity_states.md b/docs/entity_states.md index feffdfe6..35722674 100644 --- a/docs/entity_states.md +++ b/docs/entity_states.md @@ -3,7 +3,7 @@ Links contain and operate on entities. A specific entity is unique within a link ## States Each entity is in one of the following states at any given time: -* Idle: This is the default state that entities start in. +* Unshared: This is the default state that entities start in. * Activated: The entity is in the process of being pulled/deleted to/from the local side. It is only present in the source side of the link. * Received: The entity is in the process of being pulled/deleted to/from the local side. It is present in both sides of the link. * Pulled: The entity has been copied from the source to the local side. @@ -15,19 +15,19 @@ The following state diagram shows the different states that entities can be in a ```mermaid stateDiagram-v2 - [*] --> Idle - Idle --> Activated: pulled / start pull process + [*] --> Unshared + Unshared --> Activated: pulled / start pull process Activated --> Received: processed [in pull process and not flagged] / add to local Received --> Pulled: processed [in pull process and not flagged] / finish pull process Received --> Tainted: processed [in pull process and flagged] / finish pull process Pulled --> Received: deleted / start delete process Received --> Activated: processed [in delete process] / remove from local - Activated --> Idle: processed [in delete process and not flagged] / finish delete process + Activated --> Unshared: processed [in delete process and not flagged] / finish delete process Pulled --> Tainted: flagged Tainted --> Pulled: unflagged Tainted --> Received: deleted / start delete process Activated --> Deprecated: processed [flagged] / deprecate - Deprecated --> Idle: unflagged + Deprecated --> Unshared: unflagged ``` The diagram adheres to the following rule to avoid entities with invalid states due to interruptions (e.g. connection losses): @@ -39,7 +39,7 @@ Not following this rule can lead to entities in invalid states due to modifying The `pulled`, `processed` and `deleted` events are triggered by the application, whereas the `flagged` and `unflagged` events are triggered by the source side directly by modifying the persistent data. The `flagged` and `unflagged` events are also not associated with activities for the same reason. ## Processes -Idle entities can be pulled from the source side into the local side and once they are pulled they can be deleted from the local side. Activated and received entities are currently undergoing one of these two processes. The name of the specific process is associated with entities that are in the aforementioned states. This allows us to correctly transition these entities. For example without associating the process with the entity we would not be able to determine whether an activated entity should become a received one (pull) or an idle one (delete). +Unshared entities can be pulled from the source side into the local side and once they are pulled they can be deleted from the local side. Activated and received entities are currently undergoing one of these two processes. The name of the specific process is associated with entities that are in the aforementioned states. This allows us to correctly transition these entities. For example without associating the process with the entity we would not be able to determine whether an activated entity should become a received one (pull) or an unshared one (delete). ## Persistence @@ -50,7 +50,7 @@ The following table illustrates the chosen mapping: | In source | In outbound | In local | Has process | Is flagged | State | |--------------------|--------------------|--------------------|--------------------|--------------------------|------------| -| :white_check_mark: | :x: | :x: | :x: | :x: | Idle | +| :white_check_mark: | :x: | :x: | :x: | :x: | Unshared | | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: / :x: | Activated | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: / :x: | Received | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | Pulled | diff --git a/link/adapters/controller.py b/link/adapters/controller.py index f3bab2a4..881d67c4 100644 --- a/link/adapters/controller.py +++ b/link/adapters/controller.py @@ -30,6 +30,6 @@ def delete(self, primary_keys: Iterable[PrimaryKey]) -> None: """Execute the delete use-case.""" self._message_bus.handle(commands.DeleteEntities(frozenset(self._translator.to_identifiers(primary_keys)))) - def list_idle_entities(self) -> None: - """Execute the use-case that lists idle entities.""" - self._message_bus.handle(commands.ListIdleEntities()) + def list_unshared_entities(self) -> None: + """Execute the use-case that lists unshared entities.""" + self._message_bus.handle(commands.ListUnsharedEntities()) diff --git a/link/adapters/present.py b/link/adapters/present.py index fb20f570..f98a4740 100644 --- a/link/adapters/present.py +++ b/link/adapters/present.py @@ -9,15 +9,15 @@ from .identification import IdentificationTranslator -def create_idle_entities_updater( +def create_unshared_entities_updater( translator: IdentificationTranslator, update: Callable[[Iterable[PrimaryKey]], None] -) -> Callable[[events.IdleEntitiesListed], None]: - """Create a callable that when called updates the list of idle entities.""" +) -> Callable[[events.UnsharedEntitiesListed], None]: + """Create a callable that when called updates the list of unshared entities.""" - def update_idle_entities(response: events.IdleEntitiesListed) -> None: + def update_unshared_entities(response: events.UnsharedEntitiesListed) -> None: update(translator.to_primary_key(identifier) for identifier in response.identifiers) - return update_idle_entities + return update_unshared_entities def create_state_change_logger( diff --git a/link/domain/commands.py b/link/domain/commands.py index abad976e..61b584f2 100644 --- a/link/domain/commands.py +++ b/link/domain/commands.py @@ -40,5 +40,5 @@ class DeleteEntities(Command): @dataclass(frozen=True) -class ListIdleEntities(Command): +class ListUnsharedEntities(Command): """Start the delete process for the requested entities.""" diff --git a/link/domain/events.py b/link/domain/events.py index 9393f9cc..d18739c7 100644 --- a/link/domain/events.py +++ b/link/domain/events.py @@ -39,8 +39,8 @@ class StateChanged(OperationApplied): @dataclass(frozen=True) -class IdleEntitiesListed(Event): - """Idle entities in a link have been listed.""" +class UnsharedEntitiesListed(Event): + """Unshared entities in a link have been listed.""" identifiers: frozenset[Identifier] diff --git a/link/domain/link.py b/link/domain/link.py index 5f3c99f3..aebe8bec 100644 --- a/link/domain/link.py +++ b/link/domain/link.py @@ -5,7 +5,7 @@ from typing import Any, Iterable, Iterator, Mapping, Optional, Set, TypeVar from .custom_types import Identifier -from .state import STATE_MAP, Components, Entity, Idle, PersistentState, Processes, State +from .state import STATE_MAP, Components, Entity, PersistentState, Processes, State, Unshared def create_link( @@ -100,9 +100,9 @@ def __getitem__(self, identifier: Identifier) -> Entity: except StopIteration as error: raise KeyError("Requested entity not present in link") from error - def list_idle_entities(self) -> frozenset[Identifier]: - """List the identifiers of all idle entities in the link.""" - return frozenset(entity.identifier for entity in self if entity.state is Idle) + def list_unshared_entities(self) -> frozenset[Identifier]: + """List the identifiers of all unshared entities in the link.""" + return frozenset(entity.identifier for entity in self if entity.state is Unshared) def __contains__(self, entity: object) -> bool: """Check if the link contains the given entity.""" diff --git a/link/domain/state.py b/link/domain/state.py index 52f3d680..21acf50a 100644 --- a/link/domain/state.py +++ b/link/domain/state.py @@ -65,7 +65,7 @@ def register(self, state: type[State]) -> None: states = States() -class Idle(State): +class Unshared(State): """The default state of an entity.""" @classmethod @@ -74,7 +74,7 @@ def start_pull(cls, entity: Entity) -> None: return cls._transition_entity(entity, Operations.START_PULL, Activated, new_process=Processes.PULL) -states.register(Idle) +states.register(Unshared) class Activated(State): @@ -89,7 +89,7 @@ def process(cls, entity: Entity) -> None: elif entity.current_process is Processes.PULL: return transition_entity(Received) elif entity.current_process is Processes.DELETE: - return transition_entity(Idle, new_process=Processes.NONE) + return transition_entity(Unshared, new_process=Processes.NONE) raise RuntimeError @@ -172,9 +172,9 @@ class Commands(Enum): TRANSITION_MAP: dict[Transition, Commands] = { - Transition(Idle, Activated): Commands.START_PULL_PROCESS, + Transition(Unshared, Activated): Commands.START_PULL_PROCESS, Transition(Activated, Received): Commands.ADD_TO_LOCAL, - Transition(Activated, Idle): Commands.FINISH_DELETE_PROCESS, + Transition(Activated, Unshared): Commands.FINISH_DELETE_PROCESS, Transition(Activated, Deprecated): Commands.DEPRECATE, Transition(Received, Pulled): Commands.FINISH_PULL_PROCESS, Transition(Received, Tainted): Commands.FINISH_PULL_PROCESS, @@ -222,7 +222,7 @@ class PersistentState: frozenset({Components.SOURCE}), is_tainted=False, has_process=False, - ): Idle, + ): Unshared, PersistentState( frozenset({Components.SOURCE, Components.OUTBOUND}), is_tainted=False, diff --git a/link/infrastructure/link.py b/link/infrastructure/link.py index aa9fbd60..302a0249 100644 --- a/link/infrastructure/link.py +++ b/link/infrastructure/link.py @@ -10,7 +10,7 @@ from link.adapters.custom_types import PrimaryKey from link.adapters.gateway import DJLinkGateway from link.adapters.identification import IdentificationTranslator -from link.adapters.present import create_idle_entities_updater, create_state_change_logger +from link.adapters.present import create_state_change_logger, create_unshared_entities_updater from link.adapters.progress import DJProgressDisplayAdapter from link.domain import commands, events from link.service.handlers import ( @@ -20,7 +20,7 @@ inform_batch_processing_started, inform_current_process_finished, inform_next_process_started, - list_idle_entities, + list_unshared_entities, log_state_change, pull, pull_entity, @@ -59,7 +59,9 @@ def inner(obj: type) -> Any: gateway = DJLinkGateway(facade, translator) uow = UnitOfWork(gateway) source_restriction: IterationCallbackList[PrimaryKey] = IterationCallbackList() - idle_entities_updater = create_idle_entities_updater(translator, create_content_replacer(source_restriction)) + unshared_entities_updater = create_unshared_entities_updater( + translator, create_content_replacer(source_restriction) + ) logger = logging.getLogger(obj.__name__) command_handlers: CommandHandlers = {} @@ -69,8 +71,8 @@ def inner(obj: type) -> Any: command_handlers[commands.DeleteEntity] = partial(delete_entity, uow=uow, message_bus=bus) command_handlers[commands.PullEntities] = partial(pull, message_bus=bus) command_handlers[commands.DeleteEntities] = partial(delete, message_bus=bus) - command_handlers[commands.ListIdleEntities] = partial( - list_idle_entities, uow=uow, output_port=idle_entities_updater + command_handlers[commands.ListUnsharedEntities] = partial( + list_unshared_entities, uow=uow, output_port=unshared_entities_updater ) progress_view = TQDMProgressView() display = DJProgressDisplayAdapter(translator, progress_view) @@ -84,7 +86,7 @@ def inner(obj: type) -> Any: event_handlers[events.InvalidOperationRequested] = [lambda event: None] controller = DJController(bus, translator) - source_restriction.callback = controller.list_idle_entities + source_restriction.callback = controller.list_unshared_entities return create_local_endpoint(controller, tables, source_restriction, progress_view) diff --git a/link/infrastructure/mixin.py b/link/infrastructure/mixin.py index 01a36097..2d9dbfeb 100644 --- a/link/infrastructure/mixin.py +++ b/link/infrastructure/mixin.py @@ -21,7 +21,7 @@ class SourceEndpoint(Table): _progress_view: ProgressView def pull(self, *, display_progress: bool = False) -> None: - """Pull idle entities from the source table into the local table.""" + """Pull unshared entities from the source table into the local table.""" if display_progress: self._progress_view.enable() primary_keys = self.proj().fetch(as_dict=True) diff --git a/link/service/handlers.py b/link/service/handlers.py index 9442672c..321bc6e8 100644 --- a/link/service/handlers.py +++ b/link/service/handlers.py @@ -45,16 +45,16 @@ def delete(command: commands.DeleteEntities, *, message_bus: MessageBus) -> None message_bus.handle(events.BatchProcessingFinished(Processes.DELETE, command.requested)) -def list_idle_entities( - command: commands.ListIdleEntities, +def list_unshared_entities( + command: commands.ListUnsharedEntities, *, uow: UnitOfWork, - output_port: Callable[[events.IdleEntitiesListed], None], + output_port: Callable[[events.UnsharedEntitiesListed], None], ) -> None: - """List all idle entities.""" + """List all unshared entities.""" with uow: - idle = uow.link.list_idle_entities() - output_port(events.IdleEntitiesListed(idle)) + unshared = uow.link.list_unshared_entities() + output_port(events.UnsharedEntitiesListed(unshared)) def log_state_change(event: events.StateChanged, log: Callable[[events.StateChanged], None]) -> None: diff --git a/tests/integration/test_services.py b/tests/integration/test_services.py index 5aedfaf8..b8617480 100644 --- a/tests/integration/test_services.py +++ b/tests/integration/test_services.py @@ -7,7 +7,7 @@ from link.domain import commands, events from link.domain.state import Components, Processes, State, states -from link.service.handlers import delete, delete_entity, list_idle_entities, pull, pull_entity +from link.service.handlers import delete, delete_entity, list_unshared_entities, pull, pull_entity from link.service.messagebus import CommandHandlers, EventHandlers, MessageBus from link.service.uow import UnitOfWork from tests.assignments import create_assignments, create_identifiers @@ -37,7 +37,7 @@ def create_uow(state: type[State], process: Processes | None = None, is_tainted: assert process is None if state in (states.Tainted, states.Deprecated): assert is_tainted - elif state in (states.Idle, states.Pulled): + elif state in (states.Unshared, states.Pulled): assert not is_tainted if is_tainted: @@ -49,7 +49,7 @@ def create_uow(state: type[State], process: Processes | None = None, is_tainted: else: processes = {} assignments = {Components.SOURCE: {"1"}} - if state is states.Idle: + if state is states.Unshared: return UnitOfWork( FakeLinkGateway( create_assignments(assignments), tainted_identifiers=tainted_identifiers, processes=processes @@ -108,7 +108,7 @@ class EntityConfig(TypedDict): STATES: list[EntityConfig] = [ - {"state": states.Idle, "is_tainted": False, "process": None}, + {"state": states.Unshared, "is_tainted": False, "process": None}, {"state": states.Activated, "is_tainted": False, "process": Processes.PULL}, {"state": states.Activated, "is_tainted": False, "process": Processes.DELETE}, {"state": states.Activated, "is_tainted": True, "process": Processes.PULL}, @@ -126,16 +126,16 @@ class EntityConfig(TypedDict): @pytest.mark.parametrize( ("state", "expected"), [ - (STATES[0], states.Idle), - (STATES[1], states.Idle), - (STATES[2], states.Idle), + (STATES[0], states.Unshared), + (STATES[1], states.Unshared), + (STATES[2], states.Unshared), (STATES[3], states.Deprecated), (STATES[4], states.Deprecated), - (STATES[5], states.Idle), - (STATES[6], states.Idle), + (STATES[5], states.Unshared), + (STATES[6], states.Unshared), (STATES[7], states.Deprecated), (STATES[8], states.Deprecated), - (STATES[9], states.Idle), + (STATES[9], states.Unshared), (STATES[10], states.Deprecated), (STATES[11], states.Deprecated), ], @@ -173,12 +173,12 @@ def test_pulled_entity_ends_in_correct_state(state: EntityConfig, expected: type assert next(iter(uow.link)).state is expected -def test_correct_response_model_gets_passed_to_list_idle_entities_output_port() -> None: +def test_correct_response_model_gets_passed_to_list_unshared_entities_output_port() -> None: uow = UnitOfWork( FakeLinkGateway( create_assignments({Components.SOURCE: {"1", "2"}, Components.OUTBOUND: {"2"}, Components.LOCAL: {"2"}}) ) ) - output_port = FakeOutputPort[events.IdleEntitiesListed]() - list_idle_entities(commands.ListIdleEntities(), uow=uow, output_port=output_port) + output_port = FakeOutputPort[events.UnsharedEntitiesListed]() + list_unshared_entities(commands.ListUnsharedEntities(), uow=uow, output_port=output_port) assert set(output_port.response.identifiers) == create_identifiers("1") diff --git a/tests/integration/test_uow.py b/tests/integration/test_uow.py index 36cd21b1..815dc15f 100644 --- a/tests/integration/test_uow.py +++ b/tests/integration/test_uow.py @@ -24,7 +24,7 @@ def test_updates_are_applied_to_gateway_on_commit() -> None: uow.link[create_identifier("2")].delete() uow.commit() actual = {(entity.identifier, entity.state) for entity in gateway.create_link()} - expected = {(create_identifier("1"), states.Pulled), (create_identifier("2"), states.Idle)} + expected = {(create_identifier("1"), states.Pulled), (create_identifier("2"), states.Unshared)} assert actual == expected @@ -34,7 +34,7 @@ def test_updates_are_discarded_on_context_exit() -> None: uow.link[create_identifier("1")].pull() uow.link[create_identifier("2")].delete() actual = {(entity.identifier, entity.state) for entity in gateway.create_link()} - expected = {(create_identifier("1"), states.Idle), (create_identifier("2"), states.Pulled)} + expected = {(create_identifier("1"), states.Unshared), (create_identifier("2"), states.Pulled)} assert actual == expected @@ -45,7 +45,7 @@ def test_updates_are_discarded_on_rollback() -> None: uow.link[create_identifier("2")].delete() uow.rollback() actual = {(entity.identifier, entity.state) for entity in gateway.create_link()} - expected = {(create_identifier("1"), states.Idle), (create_identifier("2"), states.Pulled)} + expected = {(create_identifier("1"), states.Unshared), (create_identifier("2"), states.Pulled)} assert actual == expected @@ -131,7 +131,7 @@ def test_correct_events_are_collected() -> None: events.StateChanged( Operations.START_PULL, create_identifier("1"), - Transition(states.Idle, states.Activated), + Transition(states.Unshared, states.Activated), Commands.START_PULL_PROCESS, ), events.StateChanged( @@ -161,7 +161,7 @@ def test_correct_events_are_collected() -> None: events.StateChanged( Operations.PROCESS, create_identifier("2"), - Transition(states.Activated, states.Idle), + Transition(states.Activated, states.Unshared), Commands.FINISH_DELETE_PROCESS, ), ] diff --git a/tests/unit/entities/test_link.py b/tests/unit/entities/test_link.py index 5750c6c6..3a29a5dd 100644 --- a/tests/unit/entities/test_link.py +++ b/tests/unit/entities/test_link.py @@ -16,7 +16,7 @@ class TestCreateLink: @pytest.mark.parametrize( ("state", "expected"), [ - (states.Idle, create_identifiers("1")), + (states.Unshared, create_identifiers("1")), (states.Activated, create_identifiers("2", "7")), (states.Received, create_identifiers("3", "8")), (states.Pulled, create_identifiers("4")), diff --git a/tests/unit/entities/test_state.py b/tests/unit/entities/test_state.py index a097e303..2333fcc1 100644 --- a/tests/unit/entities/test_state.py +++ b/tests/unit/entities/test_state.py @@ -22,7 +22,7 @@ @pytest.mark.parametrize( ("identifier", "state", "operations"), [ - (create_identifier("1"), states.Idle, [Operations.START_DELETE, Operations.PROCESS]), + (create_identifier("1"), states.Unshared, [Operations.START_DELETE, Operations.PROCESS]), (create_identifier("2"), states.Activated, [Operations.START_PULL, Operations.START_DELETE]), (create_identifier("3"), states.Received, [Operations.START_PULL, Operations.START_DELETE]), (create_identifier("4"), states.Pulled, [Operations.START_PULL, Operations.PROCESS]), @@ -55,7 +55,7 @@ def test_invalid_transitions_returns_unchanged_entity( assert entity.events.pop() == result -def test_start_pulling_idle_entity_returns_correct_entity() -> None: +def test_start_pulling_unshared_entity_returns_correct_entity() -> None: link = create_link(create_assignments({Components.SOURCE: {"1"}})) entity = next(iter(link)) entity.apply(Operations.START_PULL) @@ -65,7 +65,7 @@ def test_start_pulling_idle_entity_returns_correct_entity() -> None: events.StateChanged( Operations.START_PULL, entity.identifier, - Transition(states.Idle, states.Activated), + Transition(states.Unshared, states.Activated), Commands.START_PULL_PROCESS, ) ] @@ -76,7 +76,7 @@ def test_start_pulling_idle_entity_returns_correct_entity() -> None: [ (Processes.PULL, set(), states.Received, Processes.PULL, Commands.ADD_TO_LOCAL), (Processes.PULL, create_identifiers("1"), states.Deprecated, Processes.NONE, Commands.DEPRECATE), - (Processes.DELETE, set(), states.Idle, Processes.NONE, Commands.FINISH_DELETE_PROCESS), + (Processes.DELETE, set(), states.Unshared, Processes.NONE, Commands.FINISH_DELETE_PROCESS), (Processes.DELETE, create_identifiers("1"), states.Deprecated, Processes.NONE, Commands.DEPRECATE), ], ) From 00ceed1f1991e0052c415043491dbc365abeb384 Mon Sep 17 00:00:00 2001 From: Christoph Blessing <33834216+cblessing24@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:01:14 +0100 Subject: [PATCH 2/2] Rename state "pulled" to "shared" --- docs/entity_states.md | 14 +++++++------- link/domain/state.py | 12 ++++++------ link/infrastructure/mixin.py | 2 +- link/service/handlers.py | 4 ++-- tests/integration/test_services.py | 16 ++++++++-------- tests/integration/test_uow.py | 10 +++++----- tests/unit/entities/test_link.py | 2 +- tests/unit/entities/test_state.py | 8 ++++---- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/entity_states.md b/docs/entity_states.md index 35722674..16527878 100644 --- a/docs/entity_states.md +++ b/docs/entity_states.md @@ -6,7 +6,7 @@ Each entity is in one of the following states at any given time: * Unshared: This is the default state that entities start in. * Activated: The entity is in the process of being pulled/deleted to/from the local side. It is only present in the source side of the link. * Received: The entity is in the process of being pulled/deleted to/from the local side. It is present in both sides of the link. -* Pulled: The entity has been copied from the source to the local side. +* Shared: The entity has been copied from the source to the local side. * Tainted: The entity was flagged by the source side indicating to the local side to delete it. * Deprecated: The entity was flagged by the source side and subsequently deleted by the local side. @@ -18,13 +18,13 @@ stateDiagram-v2 [*] --> Unshared Unshared --> Activated: pulled / start pull process Activated --> Received: processed [in pull process and not flagged] / add to local - Received --> Pulled: processed [in pull process and not flagged] / finish pull process + Received --> Shared: processed [in pull process and not flagged] / finish pull process Received --> Tainted: processed [in pull process and flagged] / finish pull process - Pulled --> Received: deleted / start delete process + Shared --> Received: deleted / start delete process Received --> Activated: processed [in delete process] / remove from local Activated --> Unshared: processed [in delete process and not flagged] / finish delete process - Pulled --> Tainted: flagged - Tainted --> Pulled: unflagged + Shared --> Tainted: flagged + Tainted --> Shared: unflagged Tainted --> Received: deleted / start delete process Activated --> Deprecated: processed [flagged] / deprecate Deprecated --> Unshared: unflagged @@ -39,7 +39,7 @@ Not following this rule can lead to entities in invalid states due to modifying The `pulled`, `processed` and `deleted` events are triggered by the application, whereas the `flagged` and `unflagged` events are triggered by the source side directly by modifying the persistent data. The `flagged` and `unflagged` events are also not associated with activities for the same reason. ## Processes -Unshared entities can be pulled from the source side into the local side and once they are pulled they can be deleted from the local side. Activated and received entities are currently undergoing one of these two processes. The name of the specific process is associated with entities that are in the aforementioned states. This allows us to correctly transition these entities. For example without associating the process with the entity we would not be able to determine whether an activated entity should become a received one (pull) or an unshared one (delete). +Unshared entities can be pulled from the source side into the local side and once they are shared they can be deleted from the local side. Activated and received entities are currently undergoing one of these two processes. The name of the specific process is associated with entities that are in the aforementioned states. This allows us to correctly transition these entities. For example without associating the process with the entity we would not be able to determine whether an activated entity should become a received one (pull) or an unshared one (delete). ## Persistence @@ -53,7 +53,7 @@ The following table illustrates the chosen mapping: | :white_check_mark: | :x: | :x: | :x: | :x: | Unshared | | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: / :x: | Activated | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: / :x: | Received | -| :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | Pulled | +| :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | Shared | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | Tainted | | :white_check_mark: | :white_check_mark: | :x: | :x: | :white_check_mark: | Deprecated | diff --git a/link/domain/state.py b/link/domain/state.py index 21acf50a..e39a2e11 100644 --- a/link/domain/state.py +++ b/link/domain/state.py @@ -107,7 +107,7 @@ def process(cls, entity: Entity) -> None: if entity.is_tainted: return transition_entity(Tainted, new_process=Processes.NONE) else: - return transition_entity(Pulled, new_process=Processes.NONE) + return transition_entity(Shared, new_process=Processes.NONE) elif entity.current_process is Processes.DELETE: return transition_entity(Activated) raise RuntimeError @@ -116,7 +116,7 @@ def process(cls, entity: Entity) -> None: states.register(Received) -class Pulled(State): +class Shared(State): """The state of an entity that has been copied to the local side.""" @classmethod @@ -125,7 +125,7 @@ def start_delete(cls, entity: Entity) -> None: return cls._transition_entity(entity, Operations.START_DELETE, Received, new_process=Processes.DELETE) -states.register(Pulled) +states.register(Shared) class Tainted(State): @@ -176,10 +176,10 @@ class Commands(Enum): Transition(Activated, Received): Commands.ADD_TO_LOCAL, Transition(Activated, Unshared): Commands.FINISH_DELETE_PROCESS, Transition(Activated, Deprecated): Commands.DEPRECATE, - Transition(Received, Pulled): Commands.FINISH_PULL_PROCESS, + Transition(Received, Shared): Commands.FINISH_PULL_PROCESS, Transition(Received, Tainted): Commands.FINISH_PULL_PROCESS, Transition(Received, Activated): Commands.REMOVE_FROM_LOCAL, - Transition(Pulled, Received): Commands.START_DELETE_PROCESS, + Transition(Shared, Received): Commands.START_DELETE_PROCESS, Transition(Tainted, Received): Commands.START_DELETE_PROCESS, } @@ -247,7 +247,7 @@ class PersistentState: frozenset({Components.SOURCE, Components.OUTBOUND, Components.LOCAL}), is_tainted=False, has_process=False, - ): Pulled, + ): Shared, PersistentState( frozenset({Components.SOURCE, Components.OUTBOUND, Components.LOCAL}), is_tainted=True, diff --git a/link/infrastructure/mixin.py b/link/infrastructure/mixin.py index 2d9dbfeb..05c3af78 100644 --- a/link/infrastructure/mixin.py +++ b/link/infrastructure/mixin.py @@ -70,7 +70,7 @@ class LocalEndpoint(Table): _progress_view: ProgressView def delete(self, *, display_progress: bool = False) -> None: - """Delete pulled entities from the local table.""" + """Delete shared entities from the local table.""" if display_progress: self._progress_view.enable() primary_keys = self.proj().fetch(as_dict=True) diff --git a/link/service/handlers.py b/link/service/handlers.py index 321bc6e8..c55d35a3 100644 --- a/link/service/handlers.py +++ b/link/service/handlers.py @@ -21,7 +21,7 @@ def pull_entity(command: commands.PullEntity, *, uow: UnitOfWork, message_bus: M def delete_entity(command: commands.DeleteEntity, *, uow: UnitOfWork, message_bus: MessageBus) -> None: - """Delete a pulled entity.""" + """Delete a shared entity.""" message_bus.handle(events.ProcessStarted(Processes.DELETE, command.requested)) with uow: uow.link[command.requested].delete() @@ -38,7 +38,7 @@ def pull(command: commands.PullEntities, *, message_bus: MessageBus) -> None: def delete(command: commands.DeleteEntities, *, message_bus: MessageBus) -> None: - """Delete pulled entities.""" + """Delete shared entities.""" message_bus.handle(events.BatchProcessingStarted(Processes.DELETE, command.requested)) for identifier in command.requested: message_bus.handle(commands.DeleteEntity(identifier)) diff --git a/tests/integration/test_services.py b/tests/integration/test_services.py index b8617480..997de4fe 100644 --- a/tests/integration/test_services.py +++ b/tests/integration/test_services.py @@ -37,7 +37,7 @@ def create_uow(state: type[State], process: Processes | None = None, is_tainted: assert process is None if state in (states.Tainted, states.Deprecated): assert is_tainted - elif state in (states.Unshared, states.Pulled): + elif state in (states.Unshared, states.Shared): assert not is_tainted if is_tainted: @@ -117,7 +117,7 @@ class EntityConfig(TypedDict): {"state": states.Received, "is_tainted": False, "process": Processes.DELETE}, {"state": states.Received, "is_tainted": True, "process": Processes.PULL}, {"state": states.Received, "is_tainted": True, "process": Processes.DELETE}, - {"state": states.Pulled, "is_tainted": False, "process": None}, + {"state": states.Shared, "is_tainted": False, "process": None}, {"state": states.Tainted, "is_tainted": True, "process": None}, {"state": states.Deprecated, "is_tainted": True, "process": None}, ] @@ -151,16 +151,16 @@ def test_deleted_entity_ends_in_correct_state(state: EntityConfig, expected: typ @pytest.mark.parametrize( ("state", "expected"), [ - (STATES[0], states.Pulled), - (STATES[1], states.Pulled), - (STATES[2], states.Pulled), + (STATES[0], states.Shared), + (STATES[1], states.Shared), + (STATES[2], states.Shared), (STATES[3], states.Deprecated), (STATES[4], states.Deprecated), - (STATES[5], states.Pulled), - (STATES[6], states.Pulled), + (STATES[5], states.Shared), + (STATES[6], states.Shared), (STATES[7], states.Tainted), (STATES[8], states.Deprecated), - (STATES[9], states.Pulled), + (STATES[9], states.Shared), (STATES[10], states.Tainted), (STATES[11], states.Deprecated), ], diff --git a/tests/integration/test_uow.py b/tests/integration/test_uow.py index 815dc15f..ac1c46ff 100644 --- a/tests/integration/test_uow.py +++ b/tests/integration/test_uow.py @@ -24,7 +24,7 @@ def test_updates_are_applied_to_gateway_on_commit() -> None: uow.link[create_identifier("2")].delete() uow.commit() actual = {(entity.identifier, entity.state) for entity in gateway.create_link()} - expected = {(create_identifier("1"), states.Pulled), (create_identifier("2"), states.Unshared)} + expected = {(create_identifier("1"), states.Shared), (create_identifier("2"), states.Unshared)} assert actual == expected @@ -34,7 +34,7 @@ def test_updates_are_discarded_on_context_exit() -> None: uow.link[create_identifier("1")].pull() uow.link[create_identifier("2")].delete() actual = {(entity.identifier, entity.state) for entity in gateway.create_link()} - expected = {(create_identifier("1"), states.Unshared), (create_identifier("2"), states.Pulled)} + expected = {(create_identifier("1"), states.Unshared), (create_identifier("2"), states.Shared)} assert actual == expected @@ -45,7 +45,7 @@ def test_updates_are_discarded_on_rollback() -> None: uow.link[create_identifier("2")].delete() uow.rollback() actual = {(entity.identifier, entity.state) for entity in gateway.create_link()} - expected = {(create_identifier("1"), states.Unshared), (create_identifier("2"), states.Pulled)} + expected = {(create_identifier("1"), states.Unshared), (create_identifier("2"), states.Shared)} assert actual == expected @@ -143,13 +143,13 @@ def test_correct_events_are_collected() -> None: events.StateChanged( Operations.PROCESS, create_identifier("1"), - Transition(states.Received, states.Pulled), + Transition(states.Received, states.Shared), Commands.FINISH_PULL_PROCESS, ), events.StateChanged( Operations.START_DELETE, create_identifier("2"), - Transition(states.Pulled, states.Received), + Transition(states.Shared, states.Received), Commands.START_DELETE_PROCESS, ), events.StateChanged( diff --git a/tests/unit/entities/test_link.py b/tests/unit/entities/test_link.py index 3a29a5dd..8c468fed 100644 --- a/tests/unit/entities/test_link.py +++ b/tests/unit/entities/test_link.py @@ -19,7 +19,7 @@ class TestCreateLink: (states.Unshared, create_identifiers("1")), (states.Activated, create_identifiers("2", "7")), (states.Received, create_identifiers("3", "8")), - (states.Pulled, create_identifiers("4")), + (states.Shared, create_identifiers("4")), (states.Tainted, create_identifiers("5")), (states.Deprecated, create_identifiers("6")), ], diff --git a/tests/unit/entities/test_state.py b/tests/unit/entities/test_state.py index 2333fcc1..6bb14c92 100644 --- a/tests/unit/entities/test_state.py +++ b/tests/unit/entities/test_state.py @@ -25,7 +25,7 @@ (create_identifier("1"), states.Unshared, [Operations.START_DELETE, Operations.PROCESS]), (create_identifier("2"), states.Activated, [Operations.START_PULL, Operations.START_DELETE]), (create_identifier("3"), states.Received, [Operations.START_PULL, Operations.START_DELETE]), - (create_identifier("4"), states.Pulled, [Operations.START_PULL, Operations.PROCESS]), + (create_identifier("4"), states.Shared, [Operations.START_PULL, Operations.PROCESS]), (create_identifier("5"), states.Tainted, [Operations.START_PULL, Operations.PROCESS]), ( create_identifier("6"), @@ -104,7 +104,7 @@ def test_processing_activated_entity_returns_correct_entity( @pytest.mark.parametrize( ("process", "tainted_identifiers", "new_state", "new_process", "command"), [ - (Processes.PULL, set(), states.Pulled, Processes.NONE, Commands.FINISH_PULL_PROCESS), + (Processes.PULL, set(), states.Shared, Processes.NONE, Commands.FINISH_PULL_PROCESS), (Processes.PULL, create_identifiers("1"), states.Tainted, Processes.NONE, Commands.FINISH_PULL_PROCESS), (Processes.DELETE, set(), states.Activated, Processes.DELETE, Commands.REMOVE_FROM_LOCAL), (Processes.DELETE, create_identifiers("1"), states.Activated, Processes.DELETE, Commands.REMOVE_FROM_LOCAL), @@ -132,12 +132,12 @@ def test_processing_received_entity_returns_correct_entity( assert list(entity.events) == expected_events -def test_starting_delete_on_pulled_entity_returns_correct_entity() -> None: +def test_starting_delete_on_shared_entity_returns_correct_entity() -> None: link = create_link( create_assignments({Components.SOURCE: {"1"}, Components.OUTBOUND: {"1"}, Components.LOCAL: {"1"}}) ) entity = next(iter(link)) - transition = Transition(states.Pulled, states.Received) + transition = Transition(states.Shared, states.Received) expected_events = [ events.StateChanged( Operations.START_DELETE,