From 3d6801df701597ea739982481ed191fe5b1c9c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Wed, 26 Mar 2025 15:27:26 -0300 Subject: [PATCH] remove prefix for partition key --- http-api/routes/users/__init__.py | 9 ++++++--- http-api/tests/routes/test_users.py | 24 +++++++++++++++++++++++- http-api/tests/seeds.jsonl | 4 ++-- layercake/layercake/dynamodb.py | 20 ++++++++++++++++---- layercake/pyproject.toml | 2 +- layercake/tests/test_dynamodb.py | 23 +++++++++++++++++++---- layercake/uv.lock | 2 +- 7 files changed, 68 insertions(+), 16 deletions(-) diff --git a/http-api/routes/users/__init__.py b/http-api/routes/users/__init__.py index 6188737..1971764 100644 --- a/http-api/routes/users/__init__.py +++ b/http-api/routes/users/__init__.py @@ -17,6 +17,7 @@ from layercake.dynamodb import ( KeyPair, MissingError, PartitionKey, + PrefixKey, ) from pydantic import UUID4, BaseModel, StringConstraints @@ -89,7 +90,7 @@ def get_emails(id: str): start_key = router.current_event.get_query_string_value('start_key', None) return collect.get_items( - key=KeyPair(id, 'emails'), + KeyPair(id, PrefixKey('emails#')), start_key=start_key, ) @@ -104,7 +105,9 @@ def get_logs(id: str): start_key = router.current_event.get_query_string_value('start_key', None) return collect.get_items( - key=PartitionKey(ComposeKey(id, prefix='log', delimiter=':')), + # Post-migration: uncomment to enable PartitionKey with a composite key (id with `logs` prefix). + # PartitionKey(ComposeKey(id, prefix='logs')), + PartitionKey(ComposeKey(id, prefix='log', delimiter=':')), start_key=start_key, ) @@ -119,6 +122,6 @@ def get_orgs(id: str): start_key = router.current_event.get_query_string_value('start_key', None) return collect.get_items( - key=KeyPair(id, 'orgs'), + KeyPair(id, PrefixKey('orgs#')), start_key=start_key, ) diff --git a/http-api/tests/routes/test_users.py b/http-api/tests/routes/test_users.py index bfa75b2..530ebc4 100644 --- a/http-api/tests/routes/test_users.py +++ b/http-api/tests/routes/test_users.py @@ -29,7 +29,7 @@ def test_get_emails( { 'email_verified': True, 'mx_record_exists': True, - 'sk': 'emails#sergio@somosbeta.com.br', + 'sk': 'sergio@somosbeta.com.br', 'email_primary': True, 'id': '5OxmMjL-ujoR5IMGegQz', 'create_date': '2019-03-25T00:00:00-03:00', @@ -40,6 +40,28 @@ def test_get_emails( } +def test_get_logs( + mock_app, + dynamodb_seeds, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + mock_app.users.collect = DynamoDBCollection(dynamodb_persistence_layer) + + r = mock_app.lambda_handler( + http_api_proxy( + raw_path='/users/5OxmMjL-ujoR5IMGegQz/logs', + method=HTTPMethod.GET, + ), + lambda_context, + ) + + assert r['statusCode'] == HTTPStatus.OK + + print(r['body']) + + def test_post_user( mock_app, http_api_proxy: HttpApiProxy, diff --git a/http-api/tests/seeds.jsonl b/http-api/tests/seeds.jsonl index fb171d8..9db4070 100644 --- a/http-api/tests/seeds.jsonl +++ b/http-api/tests/seeds.jsonl @@ -4,5 +4,5 @@ {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#*"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}} {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#cJtK9SsnJhKPyxESe7g3DG"}, "create_date": {"S": "2025-03-14T10:06:34.628078-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}} {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2025-03-13T16:36:50.073156-03:00"}, "name": {"S": "Beta Educação"}} -{"id": {"S": "logs#5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}} -{"id": {"S": "logs#5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}} +{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}} +{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}} diff --git a/layercake/layercake/dynamodb.py b/layercake/layercake/dynamodb.py index 4fb2dd9..3fb553d 100644 --- a/layercake/layercake/dynamodb.py +++ b/layercake/layercake/dynamodb.py @@ -669,10 +669,12 @@ class DynamoDBCollection: items = res['items'] last_key = _startkey_b64encode(res['last_key']) if res['last_key'] else None - # Remove prefix from Sort Key if `key[SK]` is a PrefixKey - if isinstance(key.get(SK), PrefixKey): - prefix = key[SK].prefix - items = [item | {SK: item[SK].removeprefix(prefix)} for item in items] + match key.get(PK), key.get(SK): + case ComposeKey(), _: # Remove prefix from Partition Key + prefix = key[PK].prefix + key[PK].delimiter + items = _remove_prefix(items, PK, prefix) + case _, PrefixKey(): # Remove prefix from Sort Key + items = _remove_prefix(items, SK, key[SK].prefix) return { 'items': items, @@ -680,6 +682,16 @@ class DynamoDBCollection: } +def _remove_prefix( + items: list[dict[str, Any]], + /, + key: str, + prefix: str, +) -> list[dict[str, Any]]: + """Remove the given prefix from the value associated with key in each item.""" + return [x | {key: x[key].removeprefix(prefix)} for x in items] + + def _startkey_b64encode(obj: dict) -> str: s = json.dumps(obj) b = urlsafe_b64encode(s.encode('utf-8')).decode('utf-8') diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 08a0568..8fd510f 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.1.1" +version = "0.1.2" description = "Add your description here" readme = "README.md" authors = [ diff --git a/layercake/tests/test_dynamodb.py b/layercake/tests/test_dynamodb.py index 7fa542a..aea3162 100644 --- a/layercake/tests/test_dynamodb.py +++ b/layercake/tests/test_dynamodb.py @@ -171,18 +171,33 @@ def test_collection_get_items( collect = DynamoDBCollection(dynamodb_persistence_layer) # This data was added from seeds - data = collect.get_items( + logs = collect.get_items( PartitionKey( ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='logs'), ), ) - assert len(data['items']) == 2 + assert len(logs['items']) == 2 + assert logs == { + 'items': [ + { + 'sk': '2024-02-08T16:42:33.776409-03:00', + 'action': 'OPEN_EMAIL', + 'id': '5OxmMjL-ujoR5IMGegQz', + }, + { + 'sk': '2019-03-25T00:00:00-03:00', + 'action': 'CLICK_EMAIL', + 'id': '5OxmMjL-ujoR5IMGegQz', + }, + ], + 'last_key': None, + } # This data was added from seeds - data = collect.get_items( + emails = collect.get_items( KeyPair('5OxmMjL-ujoR5IMGegQz', PrefixKey('emails#')), ) - assert data == { + assert emails == { 'items': [ { 'email_verified': True, diff --git a/layercake/uv.lock b/layercake/uv.lock index 8ba0209..cbfde2a 100644 --- a/layercake/uv.lock +++ b/layercake/uv.lock @@ -417,7 +417,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "aws-lambda-powertools", extra = ["all"] },