From 22b3990a66c5b425ad31bb02daea6f13b83e026a Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Wed, 5 Feb 2025 11:43:33 +0000 Subject: [PATCH 1/2] Add entity_type to Product and Product Team attributes. Remove comment Unit Tests feature tests Fix feature test Remove creation of Party Key rows --- infrastructure/swagger/05_paths.yaml | 2 +- src/api/readCpmProduct/tests/test_index.py | 3 +- src/api/readProductTeam/tests/test_index.py | 4 +- src/layers/domain/core/enum.py | 6 +++ .../v1/test_cpm_product_repository_keys_v1.py | 29 ----------- .../repository/cpm_product_repository/v1.py | 19 ++++---- .../cpm_repository/tests/model_v1.py | 4 ++ .../domain/repository/cpm_repository/v1.py | 48 +++++++++++-------- src/layers/domain/repository/keys/v1.py | 2 + .../repository/product_team_repository/v1.py | 12 +++-- 10 files changed, 63 insertions(+), 66 deletions(-) diff --git a/infrastructure/swagger/05_paths.yaml b/infrastructure/swagger/05_paths.yaml index 83b1c76ad..44daeeb9f 100644 --- a/infrastructure/swagger/05_paths.yaml +++ b/infrastructure/swagger/05_paths.yaml @@ -315,7 +315,7 @@ paths: description: | - Delete a product using a product team ID and product ID. tags: - - Core Product ID Endpointsurce (DELETE) + - Core Product ID Endpoints parameters: - $ref: "#/components/parameters/ProductTeamId" - $ref: "#/components/parameters/ProductId" diff --git a/src/api/readCpmProduct/tests/test_index.py b/src/api/readCpmProduct/tests/test_index.py index 23ad08f8e..6322d7141 100644 --- a/src/api/readCpmProduct/tests/test_index.py +++ b/src/api/readCpmProduct/tests/test_index.py @@ -13,7 +13,7 @@ TABLE_NAME = "hiya" ODS_CODE = "F5H1R" -PRODUCT_TEAM_ID = "F5H1R.641be376-3954-4339-822c-54071c9ff1a0" +PRODUCT_TEAM_ID = "641be376-3954-4339-822c-54071c9ff1a0" PRODUCT_TEAM_NAME = "product-team-name" PRODUCT_ID = "P.XXX-YYY" PRODUCT_NAME = "cpm-product-name" @@ -64,7 +64,6 @@ def test_index(version): }, } ) - response_body = json_loads(result["body"]) # Assertions for fields that must exactly match diff --git a/src/api/readProductTeam/tests/test_index.py b/src/api/readProductTeam/tests/test_index.py index d65a71766..a2a142332 100644 --- a/src/api/readProductTeam/tests/test_index.py +++ b/src/api/readProductTeam/tests/test_index.py @@ -23,7 +23,7 @@ ) def test_index(version): org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"]) - product_team = org.create_product_team_epr( + product_team = org.create_product_team( name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"] ) @@ -135,7 +135,7 @@ def test_index_no_such_product_team(version, product_id): ) def test_index_by_alias(version): org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"]) - product_team = org.create_product_team_epr( + product_team = org.create_product_team( name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"] ) diff --git a/src/layers/domain/core/enum.py b/src/layers/domain/core/enum.py index ab4f13242..8d47c7ea8 100644 --- a/src/layers/domain/core/enum.py +++ b/src/layers/domain/core/enum.py @@ -12,3 +12,9 @@ class Environment(StrEnum): REF = auto() INT = auto() PROD = auto() + + +class EntityType(StrEnum): + PRODUCT_TEAM = auto() + PRODUCT_TEAM_ALIAS = auto() + PRODUCT = auto() diff --git a/src/layers/domain/repository/cpm_product_repository/tests/v1/test_cpm_product_repository_keys_v1.py b/src/layers/domain/repository/cpm_product_repository/tests/v1/test_cpm_product_repository_keys_v1.py index e1c9b2e4f..d3a979145 100644 --- a/src/layers/domain/repository/cpm_product_repository/tests/v1/test_cpm_product_repository_keys_v1.py +++ b/src/layers/domain/repository/cpm_product_repository/tests/v1/test_cpm_product_repository_keys_v1.py @@ -1,11 +1,7 @@ import pytest from domain.core.cpm_product import CpmProduct from domain.core.product_key import ProductKey, ProductKeyType -from domain.core.root import Root from domain.repository.cpm_product_repository import CpmProductRepository -from domain.repository.errors import AlreadyExistsError - -from test_helpers.sample_data import CPM_PRODUCT_TEAM_NO_ID PARTY_KEY = "ABC-123456" @@ -17,32 +13,7 @@ def test__product_repository__add_key( party_key = ProductKey(key_type=ProductKeyType.PARTY_KEY, key_value=PARTY_KEY) product.add_key(**party_key.dict()) repository.write(product) - product_by_id = repository.read( product_team_id=product.product_team_id, id=product.id ) assert product_by_id.keys == [party_key] - - -@pytest.mark.integration -def test__product_repository__cannot_add_duplicate_key( - product: CpmProduct, repository: CpmProductRepository -): - """This test guards against Party Key clashes""" - - party_key = ProductKey(key_type=ProductKeyType.PARTY_KEY, key_value=PARTY_KEY) - product.add_key(**party_key.dict()) - repository.write(product) - - # Create a second unrelated product - org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"]) - second_product_team = org.create_product_team( - name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"] - ) - second_product = second_product_team.create_cpm_product( - name="another-cpm-product-name" - ) - second_product.add_key(**party_key.dict()) - - with pytest.raises(AlreadyExistsError): - repository.write(second_product) diff --git a/src/layers/domain/repository/cpm_product_repository/v1.py b/src/layers/domain/repository/cpm_product_repository/v1.py index 07f5c1762..a76d3bfc2 100644 --- a/src/layers/domain/repository/cpm_product_repository/v1.py +++ b/src/layers/domain/repository/cpm_product_repository/v1.py @@ -5,6 +5,7 @@ CpmProductDeletedEvent, CpmProductKeyAddedEvent, ) +from domain.core.enum import EntityType from domain.core.product_key import ProductKey from domain.repository.cpm_repository import Repository from domain.repository.keys import TableKey @@ -32,27 +33,27 @@ def handle_CpmProductCreatedEvent(self, event: CpmProductCreatedEvent): parent_key_parts=(event.product_team_id,), data=asdict(event), root=True, + row_type=EntityType.PRODUCT, ) def handle_CpmProductKeyAddedEvent(self, event: CpmProductKeyAddedEvent): # Create a copy of the Product indexed against the new key new_key = ProductKey(**event.new_key) - create_transaction = self.create_index( - id=new_key.key_value, - parent_key_parts=(event.product_team_id,), - data=asdict(event), - root=False, - ) - # Update the value of "keys" on all other copies of this Device product_keys = {ProductKey(**key) for key in event.keys} product_keys_before_update = product_keys - {new_key} update_transactions = self.update_indexes( + pk=TableKey.PRODUCT_TEAM.key(event.product_team_id), id=event.id, keys=product_keys_before_update, data={"keys": event.keys, "updated_on": event.updated_on}, ) - return [create_transaction] + update_transactions + return update_transactions def handle_CpmProductDeletedEvent(self, event: CpmProductDeletedEvent): - return self.update_indexes(id=event.id, keys=event.keys, data=asdict(event)) + return self.update_indexes( + pk=TableKey.PRODUCT_TEAM.key(event.product_team_id), + id=event.id, + keys=event.keys, + data=asdict(event), + ) diff --git a/src/layers/domain/repository/cpm_repository/tests/model_v1.py b/src/layers/domain/repository/cpm_repository/tests/model_v1.py index 951c5896c..496c89818 100644 --- a/src/layers/domain/repository/cpm_repository/tests/model_v1.py +++ b/src/layers/domain/repository/cpm_repository/tests/model_v1.py @@ -72,6 +72,8 @@ def handle_MyEventAdd(self, event: MyEventAdd): sk=MyTableKey.FOO.key(event.field), pk_read_1=MyTableKey.FOO.key(event.field), sk_read_1=MyTableKey.FOO.key(event.field), + pk_read_2=MyTableKey.FOO.key(event.field), + sk_read_2=MyTableKey.FOO.key(event.field), **asdict(event) ), ConditionExpression=ConditionExpression.MUST_NOT_EXIST, @@ -87,6 +89,8 @@ def handle_MyOtherEventAdd(self, event: MyOtherEventAdd): sk=MyTableKey.BAR.key(event.field), pk_read_1=MyTableKey.BAR.key(event.field), sk_read_1=MyTableKey.BAR.key(event.field), + pk_read_2=MyTableKey.BAR.key(event.field), + sk_read_2=MyTableKey.BAR.key(event.field), **asdict(event) ), ) diff --git a/src/layers/domain/repository/cpm_repository/v1.py b/src/layers/domain/repository/cpm_repository/v1.py index 1eaf921f6..060a31444 100644 --- a/src/layers/domain/repository/cpm_repository/v1.py +++ b/src/layers/domain/repository/cpm_repository/v1.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Generator, Iterable from domain.core.aggregate_root import AggregateRoot +from domain.core.enum import EntityType from domain.repository.errors import ItemNotFound from domain.repository.keys import KEY_SEPARATOR, TableKey from domain.repository.marshall import marshall, unmarshall @@ -106,6 +107,7 @@ def create_index( parent_key_parts: tuple[str], data: dict, root: bool, + row_type: str, table_key: TableKey = None, parent_table_keys: tuple[TableKey] = None, ) -> TransactItem: @@ -119,30 +121,37 @@ def create_index( f"Expected provide {len(parent_table_keys)} parent key parts, got {len(parent_key_parts)}" ) - write_key = table_key.key(id) - read_key = KEY_SEPARATOR.join( + sort_key = table_key.key(id) + partition_key = KEY_SEPARATOR.join( table_key.key(_id) for table_key, _id in zip(parent_table_keys, parent_key_parts) ) + item_data = { + "pk": partition_key, + "sk": sort_key, + "pk_read_1": sort_key, + "sk_read_1": sort_key, + "root": root, + "row_type": row_type, + **data, + } + + if row_type != EntityType.PRODUCT_TEAM_ALIAS: + item_data["pk_read_2"] = TableKey.ORG_CODE.key(data["ods_code"]) + item_data["sk_read_2"] = sort_key + return TransactItem( Put=TransactionStatement( TableName=self.table_name, - Item=marshall( - pk=write_key, - sk=write_key, - pk_read_1=read_key, - sk_read_1=write_key, - root=root, - **data, - ), + Item=marshall(**item_data), ConditionExpression=ConditionExpression.MUST_NOT_EXIST, ) ) - def update_indexes(self, id: str, keys: list[str], data: dict): + def update_indexes(self, pk: str, id: str, keys: list[str], data: dict): primary_keys = [ - marshall(pk=pk, sk=pk) for pk in map(self.table_key.key, [id, *keys]) + marshall(pk=pk, sk=sk) for sk in map(self.table_key.key, [id, *keys]) ] return update_transactions( table_name=self.table_name, primary_keys=primary_keys, data=data @@ -161,21 +170,18 @@ def delete_index(self, id: str): def _query( self, parent_ids: tuple[str], id: str = None, status: str = "all" ) -> list[dict]: - pk_read_1 = KEY_SEPARATOR.join( + pk = KEY_SEPARATOR.join( table_key.key(_id) for table_key, _id in zip(self.parent_table_keys, parent_ids) ) - sk_read_1 = self.table_key.key(id or "") + sk = self.table_key.key(id or "") sk_query_type = QueryType.BEGINS_WITH if id is None else QueryType.EQUALS - sk_condition = sk_query_type.format("sk_read_1", ":sk_read_1") + sk_condition = sk_query_type.format("sk", ":sk") args = { "TableName": self.table_name, - "IndexName": "idx_gsi_read_1", - "KeyConditionExpression": f"pk_read_1 = :pk_read_1 AND {sk_condition}", - "ExpressionAttributeValues": marshall( - **{":pk_read_1": pk_read_1, ":sk_read_1": sk_read_1} - ), + "KeyConditionExpression": f"pk = :pk AND {sk_condition}", + "ExpressionAttributeValues": marshall(**{":pk": pk, ":sk": sk}), } if status != "all": args["FilterExpression"] = "#status = :status" @@ -199,5 +205,7 @@ def _read(self, parent_ids: tuple[str], id: str, status: str = "all") -> ModelTy try: (item,) = items except ValueError: + if id in parent_ids: + raise ItemNotFound(id, item_type=self.model) raise ItemNotFound(*filter(bool, parent_ids), id, item_type=self.model) return self.model(**item) diff --git a/src/layers/domain/repository/keys/v1.py b/src/layers/domain/repository/keys/v1.py index abbdd9dd6..a2b53013d 100644 --- a/src/layers/domain/repository/keys/v1.py +++ b/src/layers/domain/repository/keys/v1.py @@ -26,6 +26,7 @@ def filter_and_group( class TableKey(TableKeyAction, StrEnum): PRODUCT_TEAM = "PT" + PRODUCT_TEAM_ALIAS = "PTA" CPM_SYSTEM_ID = "CSI" CPM_PRODUCT = "P" CPM_PRODUCT_STATUS = "PS" @@ -34,6 +35,7 @@ class TableKey(TableKeyAction, StrEnum): DEVICE_TAG = "DT" DEVICE_STATUS = "DS" ENVIRONMENT = "E" + ORG_CODE = "ORG" def group_by_key( diff --git a/src/layers/domain/repository/product_team_repository/v1.py b/src/layers/domain/repository/product_team_repository/v1.py index 78312fbc1..0031cf5e0 100644 --- a/src/layers/domain/repository/product_team_repository/v1.py +++ b/src/layers/domain/repository/product_team_repository/v1.py @@ -1,4 +1,5 @@ from attr import asdict +from domain.core.enum import EntityType from domain.core.product_team import ProductTeam, ProductTeamCreatedEvent from domain.core.product_team_key import ProductTeamKey from domain.repository.cpm_repository import Repository @@ -16,23 +17,28 @@ def __init__(self, table_name: str, dynamodb_client): ) def read(self, id: str) -> ProductTeam: - return super()._read(parent_ids=("",), id=id, status="active") + return super()._read(parent_ids=(id,), id=id, status="active") def search(self) -> list[ProductTeam]: return super()._search(parent_ids=("",)) def handle_ProductTeamCreatedEvent(self, event: ProductTeamCreatedEvent): create_root_transaction = self.create_index( - id=event.id, parent_key_parts=("",), data=asdict(event), root=True + id=event.id, + parent_key_parts=(event.id,), + data=asdict(event), + root=True, + row_type=EntityType.PRODUCT_TEAM, ) keys = {ProductTeamKey(**key) for key in event.keys} create_key_transactions = [ self.create_index( id=key.key_value, - parent_key_parts=("",), + parent_key_parts=(key.key_value,), data=asdict(event), root=False, + row_type=EntityType.PRODUCT_TEAM_ALIAS, ) for key in keys ] From baab92d3eab210e3ab0b167fc1d37a9e86fabad2 Mon Sep 17 00:00:00 2001 From: Megan Date: Thu, 13 Feb 2025 15:59:27 +0000 Subject: [PATCH 2/2] release/2025-02-13 Release branch created --- CHANGELOG.md | 3 +++ VERSION | 2 +- changelog/2025-02-13.md | 1 + pyproject.toml | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog/2025-02-13.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce68960d..57145af00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 2025-02-13 +- [PI-767] Table design change + ## 2025-02-12 - [PI-762] UI Demo POC diff --git a/VERSION b/VERSION index 3f36a6b99..f71705fc2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2025.02.12 +2025.02.13 diff --git a/changelog/2025-02-13.md b/changelog/2025-02-13.md new file mode 100644 index 000000000..42a21d2eb --- /dev/null +++ b/changelog/2025-02-13.md @@ -0,0 +1 @@ +- [PI-767] Table design change diff --git a/pyproject.toml b/pyproject.toml index 24735227d..35f8be3da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "connecting-party-manager" -version = "2025.02.12" +version = "2025.02.13" description = "Repository for the Connecting Party Manager API and related services" authors = ["NHS England"] license = "LICENSE.md"