Skip to content

Commit 473c918

Browse files
committed
Merge branch 'feature/PI-767-update_cpm_table_design' into release/2025-02-13
2 parents baab92d + 22b3990 commit 473c918

File tree

10 files changed

+63
-66
lines changed

10 files changed

+63
-66
lines changed

infrastructure/swagger/05_paths.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ paths:
315315
description: |
316316
- Delete a product using a product team ID and product ID.
317317
tags:
318-
- Core Product ID Endpointsurce (DELETE)
318+
- Core Product ID Endpoints
319319
parameters:
320320
- $ref: "#/components/parameters/ProductTeamId"
321321
- $ref: "#/components/parameters/ProductId"

src/api/readCpmProduct/tests/test_index.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
TABLE_NAME = "hiya"
1515
ODS_CODE = "F5H1R"
16-
PRODUCT_TEAM_ID = "F5H1R.641be376-3954-4339-822c-54071c9ff1a0"
16+
PRODUCT_TEAM_ID = "641be376-3954-4339-822c-54071c9ff1a0"
1717
PRODUCT_TEAM_NAME = "product-team-name"
1818
PRODUCT_ID = "P.XXX-YYY"
1919
PRODUCT_NAME = "cpm-product-name"
@@ -64,7 +64,6 @@ def test_index(version):
6464
},
6565
}
6666
)
67-
6867
response_body = json_loads(result["body"])
6968

7069
# Assertions for fields that must exactly match

src/api/readProductTeam/tests/test_index.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
def test_index(version):
2525
org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"])
26-
product_team = org.create_product_team_epr(
26+
product_team = org.create_product_team(
2727
name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"]
2828
)
2929

@@ -135,7 +135,7 @@ def test_index_no_such_product_team(version, product_id):
135135
)
136136
def test_index_by_alias(version):
137137
org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"])
138-
product_team = org.create_product_team_epr(
138+
product_team = org.create_product_team(
139139
name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"]
140140
)
141141

src/layers/domain/core/enum.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ class Environment(StrEnum):
1212
REF = auto()
1313
INT = auto()
1414
PROD = auto()
15+
16+
17+
class EntityType(StrEnum):
18+
PRODUCT_TEAM = auto()
19+
PRODUCT_TEAM_ALIAS = auto()
20+
PRODUCT = auto()
Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import pytest
22
from domain.core.cpm_product import CpmProduct
33
from domain.core.product_key import ProductKey, ProductKeyType
4-
from domain.core.root import Root
54
from domain.repository.cpm_product_repository import CpmProductRepository
6-
from domain.repository.errors import AlreadyExistsError
7-
8-
from test_helpers.sample_data import CPM_PRODUCT_TEAM_NO_ID
95

106
PARTY_KEY = "ABC-123456"
117

@@ -17,32 +13,7 @@ def test__product_repository__add_key(
1713
party_key = ProductKey(key_type=ProductKeyType.PARTY_KEY, key_value=PARTY_KEY)
1814
product.add_key(**party_key.dict())
1915
repository.write(product)
20-
2116
product_by_id = repository.read(
2217
product_team_id=product.product_team_id, id=product.id
2318
)
2419
assert product_by_id.keys == [party_key]
25-
26-
27-
@pytest.mark.integration
28-
def test__product_repository__cannot_add_duplicate_key(
29-
product: CpmProduct, repository: CpmProductRepository
30-
):
31-
"""This test guards against Party Key clashes"""
32-
33-
party_key = ProductKey(key_type=ProductKeyType.PARTY_KEY, key_value=PARTY_KEY)
34-
product.add_key(**party_key.dict())
35-
repository.write(product)
36-
37-
# Create a second unrelated product
38-
org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"])
39-
second_product_team = org.create_product_team(
40-
name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"]
41-
)
42-
second_product = second_product_team.create_cpm_product(
43-
name="another-cpm-product-name"
44-
)
45-
second_product.add_key(**party_key.dict())
46-
47-
with pytest.raises(AlreadyExistsError):
48-
repository.write(second_product)

src/layers/domain/repository/cpm_product_repository/v1.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
CpmProductDeletedEvent,
66
CpmProductKeyAddedEvent,
77
)
8+
from domain.core.enum import EntityType
89
from domain.core.product_key import ProductKey
910
from domain.repository.cpm_repository import Repository
1011
from domain.repository.keys import TableKey
@@ -32,27 +33,27 @@ def handle_CpmProductCreatedEvent(self, event: CpmProductCreatedEvent):
3233
parent_key_parts=(event.product_team_id,),
3334
data=asdict(event),
3435
root=True,
36+
row_type=EntityType.PRODUCT,
3537
)
3638

3739
def handle_CpmProductKeyAddedEvent(self, event: CpmProductKeyAddedEvent):
3840
# Create a copy of the Product indexed against the new key
3941
new_key = ProductKey(**event.new_key)
40-
create_transaction = self.create_index(
41-
id=new_key.key_value,
42-
parent_key_parts=(event.product_team_id,),
43-
data=asdict(event),
44-
root=False,
45-
)
46-
4742
# Update the value of "keys" on all other copies of this Device
4843
product_keys = {ProductKey(**key) for key in event.keys}
4944
product_keys_before_update = product_keys - {new_key}
5045
update_transactions = self.update_indexes(
46+
pk=TableKey.PRODUCT_TEAM.key(event.product_team_id),
5147
id=event.id,
5248
keys=product_keys_before_update,
5349
data={"keys": event.keys, "updated_on": event.updated_on},
5450
)
55-
return [create_transaction] + update_transactions
51+
return update_transactions
5652

5753
def handle_CpmProductDeletedEvent(self, event: CpmProductDeletedEvent):
58-
return self.update_indexes(id=event.id, keys=event.keys, data=asdict(event))
54+
return self.update_indexes(
55+
pk=TableKey.PRODUCT_TEAM.key(event.product_team_id),
56+
id=event.id,
57+
keys=event.keys,
58+
data=asdict(event),
59+
)

src/layers/domain/repository/cpm_repository/tests/model_v1.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def handle_MyEventAdd(self, event: MyEventAdd):
7272
sk=MyTableKey.FOO.key(event.field),
7373
pk_read_1=MyTableKey.FOO.key(event.field),
7474
sk_read_1=MyTableKey.FOO.key(event.field),
75+
pk_read_2=MyTableKey.FOO.key(event.field),
76+
sk_read_2=MyTableKey.FOO.key(event.field),
7577
**asdict(event)
7678
),
7779
ConditionExpression=ConditionExpression.MUST_NOT_EXIST,
@@ -87,6 +89,8 @@ def handle_MyOtherEventAdd(self, event: MyOtherEventAdd):
8789
sk=MyTableKey.BAR.key(event.field),
8890
pk_read_1=MyTableKey.BAR.key(event.field),
8991
sk_read_1=MyTableKey.BAR.key(event.field),
92+
pk_read_2=MyTableKey.BAR.key(event.field),
93+
sk_read_2=MyTableKey.BAR.key(event.field),
9094
**asdict(event)
9195
),
9296
)

src/layers/domain/repository/cpm_repository/v1.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import TYPE_CHECKING, Generator, Iterable
44

55
from domain.core.aggregate_root import AggregateRoot
6+
from domain.core.enum import EntityType
67
from domain.repository.errors import ItemNotFound
78
from domain.repository.keys import KEY_SEPARATOR, TableKey
89
from domain.repository.marshall import marshall, unmarshall
@@ -106,6 +107,7 @@ def create_index(
106107
parent_key_parts: tuple[str],
107108
data: dict,
108109
root: bool,
110+
row_type: str,
109111
table_key: TableKey = None,
110112
parent_table_keys: tuple[TableKey] = None,
111113
) -> TransactItem:
@@ -119,30 +121,37 @@ def create_index(
119121
f"Expected provide {len(parent_table_keys)} parent key parts, got {len(parent_key_parts)}"
120122
)
121123

122-
write_key = table_key.key(id)
123-
read_key = KEY_SEPARATOR.join(
124+
sort_key = table_key.key(id)
125+
partition_key = KEY_SEPARATOR.join(
124126
table_key.key(_id)
125127
for table_key, _id in zip(parent_table_keys, parent_key_parts)
126128
)
127129

130+
item_data = {
131+
"pk": partition_key,
132+
"sk": sort_key,
133+
"pk_read_1": sort_key,
134+
"sk_read_1": sort_key,
135+
"root": root,
136+
"row_type": row_type,
137+
**data,
138+
}
139+
140+
if row_type != EntityType.PRODUCT_TEAM_ALIAS:
141+
item_data["pk_read_2"] = TableKey.ORG_CODE.key(data["ods_code"])
142+
item_data["sk_read_2"] = sort_key
143+
128144
return TransactItem(
129145
Put=TransactionStatement(
130146
TableName=self.table_name,
131-
Item=marshall(
132-
pk=write_key,
133-
sk=write_key,
134-
pk_read_1=read_key,
135-
sk_read_1=write_key,
136-
root=root,
137-
**data,
138-
),
147+
Item=marshall(**item_data),
139148
ConditionExpression=ConditionExpression.MUST_NOT_EXIST,
140149
)
141150
)
142151

143-
def update_indexes(self, id: str, keys: list[str], data: dict):
152+
def update_indexes(self, pk: str, id: str, keys: list[str], data: dict):
144153
primary_keys = [
145-
marshall(pk=pk, sk=pk) for pk in map(self.table_key.key, [id, *keys])
154+
marshall(pk=pk, sk=sk) for sk in map(self.table_key.key, [id, *keys])
146155
]
147156
return update_transactions(
148157
table_name=self.table_name, primary_keys=primary_keys, data=data
@@ -161,21 +170,18 @@ def delete_index(self, id: str):
161170
def _query(
162171
self, parent_ids: tuple[str], id: str = None, status: str = "all"
163172
) -> list[dict]:
164-
pk_read_1 = KEY_SEPARATOR.join(
173+
pk = KEY_SEPARATOR.join(
165174
table_key.key(_id)
166175
for table_key, _id in zip(self.parent_table_keys, parent_ids)
167176
)
168-
sk_read_1 = self.table_key.key(id or "")
177+
sk = self.table_key.key(id or "")
169178

170179
sk_query_type = QueryType.BEGINS_WITH if id is None else QueryType.EQUALS
171-
sk_condition = sk_query_type.format("sk_read_1", ":sk_read_1")
180+
sk_condition = sk_query_type.format("sk", ":sk")
172181
args = {
173182
"TableName": self.table_name,
174-
"IndexName": "idx_gsi_read_1",
175-
"KeyConditionExpression": f"pk_read_1 = :pk_read_1 AND {sk_condition}",
176-
"ExpressionAttributeValues": marshall(
177-
**{":pk_read_1": pk_read_1, ":sk_read_1": sk_read_1}
178-
),
183+
"KeyConditionExpression": f"pk = :pk AND {sk_condition}",
184+
"ExpressionAttributeValues": marshall(**{":pk": pk, ":sk": sk}),
179185
}
180186
if status != "all":
181187
args["FilterExpression"] = "#status = :status"
@@ -199,5 +205,7 @@ def _read(self, parent_ids: tuple[str], id: str, status: str = "all") -> ModelTy
199205
try:
200206
(item,) = items
201207
except ValueError:
208+
if id in parent_ids:
209+
raise ItemNotFound(id, item_type=self.model)
202210
raise ItemNotFound(*filter(bool, parent_ids), id, item_type=self.model)
203211
return self.model(**item)

src/layers/domain/repository/keys/v1.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def filter_and_group(
2626

2727
class TableKey(TableKeyAction, StrEnum):
2828
PRODUCT_TEAM = "PT"
29+
PRODUCT_TEAM_ALIAS = "PTA"
2930
CPM_SYSTEM_ID = "CSI"
3031
CPM_PRODUCT = "P"
3132
CPM_PRODUCT_STATUS = "PS"
@@ -34,6 +35,7 @@ class TableKey(TableKeyAction, StrEnum):
3435
DEVICE_TAG = "DT"
3536
DEVICE_STATUS = "DS"
3637
ENVIRONMENT = "E"
38+
ORG_CODE = "ORG"
3739

3840

3941
def group_by_key(

src/layers/domain/repository/product_team_repository/v1.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from attr import asdict
2+
from domain.core.enum import EntityType
23
from domain.core.product_team import ProductTeam, ProductTeamCreatedEvent
34
from domain.core.product_team_key import ProductTeamKey
45
from domain.repository.cpm_repository import Repository
@@ -16,23 +17,28 @@ def __init__(self, table_name: str, dynamodb_client):
1617
)
1718

1819
def read(self, id: str) -> ProductTeam:
19-
return super()._read(parent_ids=("",), id=id, status="active")
20+
return super()._read(parent_ids=(id,), id=id, status="active")
2021

2122
def search(self) -> list[ProductTeam]:
2223
return super()._search(parent_ids=("",))
2324

2425
def handle_ProductTeamCreatedEvent(self, event: ProductTeamCreatedEvent):
2526
create_root_transaction = self.create_index(
26-
id=event.id, parent_key_parts=("",), data=asdict(event), root=True
27+
id=event.id,
28+
parent_key_parts=(event.id,),
29+
data=asdict(event),
30+
root=True,
31+
row_type=EntityType.PRODUCT_TEAM,
2732
)
2833

2934
keys = {ProductTeamKey(**key) for key in event.keys}
3035
create_key_transactions = [
3136
self.create_index(
3237
id=key.key_value,
33-
parent_key_parts=("",),
38+
parent_key_parts=(key.key_value,),
3439
data=asdict(event),
3540
root=False,
41+
row_type=EntityType.PRODUCT_TEAM_ALIAS,
3642
)
3743
for key in keys
3844
]

0 commit comments

Comments
 (0)