From a7ee7873785a99c99dc2f3fdb27261050628b4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 23 May 2025 10:30:54 -0300 Subject: [PATCH] fix retain key --- batch-jobs/samconfig.toml | 2 +- batch-jobs/template.yaml | 2 +- certs/cert.html | 8 +-- certs/style.css | 72 +------------------ http-api/.env.sample | 9 --- http-api/app/auth.py | 4 +- http-api/app/boto3clients.py | 17 ++--- http-api/app/konviva.py | 2 +- http-api/app/middlewares/tenant_middelware.py | 6 +- http-api/app/routes/courses/__init__.py | 2 +- http-api/app/routes/orders/__init__.py | 2 +- http-api/app/routes/orgs/policies.py | 6 +- http-api/app/routes/users/__init__.py | 2 +- http-api/app/routes/users/emails.py | 2 +- http-api/app/routes/users/logs.py | 2 +- http-api/app/routes/users/orgs.py | 2 +- http-api/app/rules/course.py | 5 +- http-api/app/rules/org.py | 8 +-- http-api/app/rules/user.py | 41 +++++------ http-api/env.json.sample | 8 +-- http-api/template.yaml | 4 +- http-api/tests/conftest.py | 22 +++--- http-api/uv.lock | 2 +- layercake/layercake/dynamodb.py | 30 +++++--- layercake/pyproject.toml | 2 +- layercake/tests/test_dynamodb.py | 21 +++++- 26 files changed, 117 insertions(+), 166 deletions(-) delete mode 100644 http-api/.env.sample diff --git a/batch-jobs/samconfig.toml b/batch-jobs/samconfig.toml index d60f62d..4b81c22 100644 --- a/batch-jobs/samconfig.toml +++ b/batch-jobs/samconfig.toml @@ -2,7 +2,7 @@ version = 0.1 [default.deploy.parameters] stack_name = "saladeaula-batch-jobs" resolve_s3 = true -s3_prefix = "batchjobs" +s3_prefix = "batch_jobs" region = "sa-east-1" confirm_changeset = false capabilities = "CAPABILITY_IAM" diff --git a/batch-jobs/template.yaml b/batch-jobs/template.yaml index 93b489d..9ba846f 100644 --- a/batch-jobs/template.yaml +++ b/batch-jobs/template.yaml @@ -9,7 +9,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:53 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:55 Environment: Variables: TZ: America/Sao_Paulo diff --git a/certs/cert.html b/certs/cert.html index 87838d8..ead6f74 100644 --- a/certs/cert.html +++ b/certs/cert.html @@ -16,13 +16,11 @@

{{ name }}

Portador(a) do CPF {{ cpf }} , concluiu o curso - de {{ course }} com aproveitamento de + de NR-10 Complementar (SEP) com aproveitamento + de {{ progress }}%

-

- Realizado entre {{ start_date }} e {{ finish_date }}, com - validade até {{ due_date }} -

+

Realizado entre {{ start_date }} e {{ finish_date }}

Florianópolis, SC, {{ today }}

diff --git a/certs/style.css b/certs/style.css index cff5985..04e5cf3 100644 --- a/certs/style.css +++ b/certs/style.css @@ -5,10 +5,6 @@ html, body, div, -span, -applet, -object, -iframe, h1, h2, h3, @@ -16,73 +12,7 @@ h4, h5, h6, p, -blockquote, -pre, -a, -abbr, -acronym, -address, -big, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -b, -u, -i, -center, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -embed, -figure, -figcaption, -footer, -header, -hgroup, -menu, -nav, -output, -ruby, -section, -summary, -time, -mark, -audio, -video { +a { margin: 0; padding: 0; border: 0; diff --git a/http-api/.env.sample b/http-api/.env.sample deleted file mode 100644 index 051dae7..0000000 --- a/http-api/.env.sample +++ /dev/null @@ -1,9 +0,0 @@ -# If UV does not load this file, try running `export UV_ENV_FILE=.env` -# See more details at https://docs.astral.sh/uv/configuration/files/#env - -KONVIVA_API_URL=https://saladeaula.digital -KONVIVA_SECRET_KEY= -MEILISEARCH_HOST= -MEILISEARCH_API_KEY= -DYNAMODB_PARTITION_KEY=id -DYNAMODB_SORT_KEY=sk diff --git a/http-api/app/auth.py b/http-api/app/auth.py index 4755616..fd0293b 100644 --- a/http-api/app/auth.py +++ b/http-api/app/auth.py @@ -26,7 +26,6 @@ Example from dataclasses import asdict, dataclass from typing import Any -import boto3 from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.data_classes import event_source from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import ( @@ -38,7 +37,7 @@ from botocore.endpoint_provider import Enum from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair from layercake.funcs import pick -from boto3clients import dynamodb_client +from boto3clients import dynamodb_client, idp_client from cognito import get_user from conf import USER_TABLE @@ -46,7 +45,6 @@ APIKEY_PREFIX = 'sk-' tracer = Tracer() logger = Logger(__name__) -idp_client = boto3.client('cognito-idp') user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_collect = DynamoDBCollection(user_layer) diff --git a/http-api/app/boto3clients.py b/http-api/app/boto3clients.py index 4ce8bbd..7975d25 100644 --- a/http-api/app/boto3clients.py +++ b/http-api/app/boto3clients.py @@ -2,15 +2,16 @@ import os import boto3 -DYNAMODB_ENDPOINT_URL: str | None = None -# Only when running `sam local start-api` -if 'AWS_SAM_LOCAL' in os.environ: - DYNAMODB_ENDPOINT_URL = 'http://host.docker.internal:8000' +def get_dynamodb_client(): + sam_local = os.getenv('AWS_SAM_LOCAL') -# Only when running `pytest` -if 'PYTEST_VERSION' in os.environ: - DYNAMODB_ENDPOINT_URL = 'http://127.0.0.1:8000' + if os.getenv('AWS_LAMBDA_FUNCTION_NAME') and not sam_local: + return boto3.client('dynamodb') -dynamodb_client = boto3.client('dynamodb', endpoint_url=DYNAMODB_ENDPOINT_URL) + url = 'host.docker.internal' if sam_local else 'localhost' + return boto3.client('dynamodb', endpoint_url=f'http://{url}:8000') + + +dynamodb_client = get_dynamodb_client() idp_client = boto3.client('cognito-idp') diff --git a/http-api/app/konviva.py b/http-api/app/konviva.py index 9a6c43c..bc1d193 100644 --- a/http-api/app/konviva.py +++ b/http-api/app/konviva.py @@ -2,9 +2,9 @@ from dataclasses import asdict, dataclass from urllib.parse import quote as urlquote from urllib.parse import urlencode, urlparse +import requests from aws_lambda_powertools.event_handler.exceptions import BadRequestError from glom import glom -import requests from conf import KONVIVA_API_URL, KONVIVA_SECRET_KEY diff --git a/http-api/app/middlewares/tenant_middelware.py b/http-api/app/middlewares/tenant_middelware.py index 80554ca..af2ce2e 100644 --- a/http-api/app/middlewares/tenant_middelware.py +++ b/http-api/app/middlewares/tenant_middelware.py @@ -119,12 +119,12 @@ def _tenant( # Ensure user has ACL collect.get_item( KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')), - exception_cls=ForbiddenError, + exc_cls=ForbiddenError, ) # For root tenant, provide the default Tenant if tenant_id == '*': return Tenant(id=tenant_id, name='default') - obj = collect.get_item(KeyPair(tenant_id, '0'), exception_cls=NotFoundError) - return Tenant.parse_obj(obj) + obj = collect.get_item(KeyPair(tenant_id, '0'), exc_cls=NotFoundError) + return Tenant.model_validate(obj) diff --git a/http-api/app/routes/courses/__init__.py b/http-api/app/routes/courses/__init__.py index 3a7c3e1..406a115 100644 --- a/http-api/app/routes/courses/__init__.py +++ b/http-api/app/routes/courses/__init__.py @@ -76,7 +76,7 @@ def post_course(payload: Course): def get_course(id: str): return course_collect.get_item( KeyPair(id, '0'), - exception_cls=NotFoundError, + exc_cls=NotFoundError, ) diff --git a/http-api/app/routes/orders/__init__.py b/http-api/app/routes/orders/__init__.py index 62857f2..bbb8593 100644 --- a/http-api/app/routes/orders/__init__.py +++ b/http-api/app/routes/orders/__init__.py @@ -17,7 +17,7 @@ from conf import ELASTIC_CONN, ORDER_TABLE router = Router() order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) -order_collect = DynamoDBCollection(order_layer, exception_cls=BadRequestError) +order_collect = DynamoDBCollection(order_layer, exc_cls=BadRequestError) elastic_client = Elasticsearch(**ELASTIC_CONN) diff --git a/http-api/app/routes/orgs/policies.py b/http-api/app/routes/orgs/policies.py index 4915382..a7d2f2f 100644 --- a/http-api/app/routes/orgs/policies.py +++ b/http-api/app/routes/orgs/policies.py @@ -20,7 +20,7 @@ from rules.org import update_policies router = Router() org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) -org_collect = DynamoDBCollection(org_layer, exception_cls=BadRequestError) +org_collect = DynamoDBCollection(org_layer, exc_cls=BadRequestError) @router.get( @@ -31,7 +31,9 @@ org_collect = DynamoDBCollection(org_layer, exception_cls=BadRequestError) ) def get_policies(id: str): return org_collect.get_items( - TransactKey(id) + SortKey('billing_policy') + SortKey('payment_policy'), + TransactKey(id) + + SortKey('metadata#billing_policy', remove_prefix='metadata#') + + SortKey('metadata#payment_policy', remove_prefix='metadata#'), flatten_top=False, ) diff --git a/http-api/app/routes/users/__init__.py b/http-api/app/routes/users/__init__.py index 8f80992..1090db4 100644 --- a/http-api/app/routes/users/__init__.py +++ b/http-api/app/routes/users/__init__.py @@ -39,7 +39,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): router = Router() user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) -user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError) +user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError) elastic_client = Elasticsearch(**ELASTIC_CONN) diff --git a/http-api/app/routes/users/emails.py b/http-api/app/routes/users/emails.py index 0f2cb9c..31ab9b4 100644 --- a/http-api/app/routes/users/emails.py +++ b/http-api/app/routes/users/emails.py @@ -25,7 +25,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ... router = Router() user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) -user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError) +user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError) @router.get( diff --git a/http-api/app/routes/users/logs.py b/http-api/app/routes/users/logs.py index 9d95182..b5ce9f7 100644 --- a/http-api/app/routes/users/logs.py +++ b/http-api/app/routes/users/logs.py @@ -23,7 +23,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ... router = Router() user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) -user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError) +user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError) @router.get( diff --git a/http-api/app/routes/users/orgs.py b/http-api/app/routes/users/orgs.py index 0330651..c02e9f2 100644 --- a/http-api/app/routes/users/orgs.py +++ b/http-api/app/routes/users/orgs.py @@ -26,7 +26,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ... router = Router() user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) -user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError) +user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError) @router.get( diff --git a/http-api/app/rules/course.py b/http-api/app/rules/course.py index c00b543..567522f 100644 --- a/http-api/app/rules/course.py +++ b/http-api/app/rules/course.py @@ -23,7 +23,7 @@ def create_course( transact.put( item={ 'id': course.id, - 'sk': 'tenant', + 'sk': 'metadata#tenant', 'org_id': org.id, 'name': org.name, 'create_date': now_, @@ -42,7 +42,8 @@ def update_course( transact = TransactItems(persistence_layer.table_name) transact.update( key=KeyPair(id, '0'), - update_expr='SET #name = :name, access_period = :access_period, cert = :cert, update_date = :update_date', + update_expr='SET #name = :name, access_period = :access_period, \ + cert = :cert, update_date = :update_date', expr_attr_names={ '#name': 'name', }, diff --git a/http-api/app/rules/org.py b/http-api/app/rules/org.py index 3415ce0..d8e1cfe 100644 --- a/http-api/app/rules/org.py +++ b/http-api/app/rules/org.py @@ -17,24 +17,24 @@ def update_policies( transact.put( item={ 'id': id, - 'sk': 'payment_policy', + 'sk': 'metadata#payment_policy', 'create_date': now_, } | payment_policy ) else: - transact.delete(key=KeyPair(id, 'payment_policy')) + transact.delete(key=KeyPair(id, 'metadata#payment_policy')) if billing_policy: transact.put( item={ 'id': id, - 'sk': 'billing_policy', + 'sk': 'metadata#billing_policy', 'create_date': now_, } | billing_policy ) else: - transact.delete(key=KeyPair(id, 'billing_policy')) + transact.delete(key=KeyPair(id, 'metadata#billing_policy')) return persistence_layer.transact_write_items(transact) diff --git a/http-api/app/rules/user.py b/http-api/app/rules/user.py index 6ef124f..65a7535 100644 --- a/http-api/app/rules/user.py +++ b/http-api/app/rules/user.py @@ -4,7 +4,6 @@ from typing import TypedDict from aws_lambda_powertools.event_handler.exceptions import ( BadRequestError, ) -from botocore.exceptions import ClientError from botocore.tokens import timedelta from layercake.dateutils import now, ttl from layercake.dynamodb import ( @@ -14,11 +13,6 @@ from layercake.dynamodb import ( TransactItems, ) - -class CPFConflictError(BadRequestError): - pass - - User = TypedDict('User', {'id': str, 'name': str, 'cpf': str}) @@ -60,6 +54,10 @@ def update_user( cond_expr='attribute_not_exists(sk)', ) + class CPFConflictError(BadRequestError): + def __init__(self, msg: str): + super().__init__('Cpf already exists') + if user.cpf != old_cpf: transact.put( item={ @@ -69,18 +67,14 @@ def update_user( 'create_date': now_, }, cond_expr='attribute_not_exists(sk)', + exc_cls=CPFConflictError, ) # Ensures that the old CPF is discarded if old_cpf: transact.delete(key=KeyPair('cpf', old_cpf)) - try: - persistence_layer.transact_write_items(transact) - except ClientError: - raise CPFConflictError('CPF is already in use.') - else: - return True + return persistence_layer.transact_write_items(transact) def add_email( @@ -109,6 +103,11 @@ def add_email( }, cond_expr='attribute_not_exists(sk)', ) + + class EmailConflictError(BadRequestError): + def __init__(self, msg: str): + super().__init__('Email already exists') + transact.put( item={ 'id': 'email', @@ -117,12 +116,10 @@ def add_email( 'create_date': now_, }, cond_expr='attribute_not_exists(sk)', + exc_cls=EmailConflictError, ) - try: - return persistence_layer.transact_write_items(transact) - except ClientError: - raise BadRequestError('Email already exists.') + return persistence_layer.transact_write_items(transact) def del_email( @@ -141,6 +138,7 @@ def del_email( key=KeyPair(id, ComposeKey(email, prefix='emails')), cond_expr='email_primary <> :primary', expr_attr_values={':primary': True}, + exc_cls=BadRequestError, ) transact.update( key=KeyPair(id, '0'), @@ -150,10 +148,7 @@ def del_email( }, ) - try: - return persistence_layer.transact_write_items(transact) - except ClientError: - raise BadRequestError('Cannot remove the primary email.') + return persistence_layer.transact_write_items(transact) def set_email_as_primary( @@ -188,10 +183,8 @@ def set_email_as_primary( ) transact.update( key=KeyPair(id, '0'), - update_expr=( - 'SET email = :email, email_verified = :email_verified, ' - 'update_date = :update_date' - ), + update_expr='SET email = :email, email_verified = :email_verified, \ + update_date = :update_date', expr_attr_values={ ':email': new_email, ':email_verified': email_verified, diff --git a/http-api/env.json.sample b/http-api/env.json.sample index 0243a3f..0616ef8 100644 --- a/http-api/env.json.sample +++ b/http-api/env.json.sample @@ -1,9 +1,9 @@ { "Parameters": { "KONVIVA_API_KEY": "", - "USER_TABLE": "test-users", - "ORDER_TABLE": "test-orders", - "ENROLLMENT_TABLE": "test-enrollments" - "COURSE_TABLE": "test-courses" + "USER_TABLE": "", + "ORDER_TABLE": "", + "ENROLLMENT_TABLE": "" + "COURSE_TABLE": "" } } diff --git a/http-api/template.yaml b/http-api/template.yaml index 4eaed09..35840a7 100644 --- a/http-api/template.yaml +++ b/http-api/template.yaml @@ -23,7 +23,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:48 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:59 Environment: Variables: TZ: America/Sao_Paulo @@ -39,7 +39,7 @@ Globals: ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}" KONVIVA_API_URL: https://saladeaula.digital KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}" - MEILISEARCH_HOST: https://meili.vps.eduseg.com.br + MEILISEARCH_HOST: https://meili.eduseg.com.br MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}" Resources: diff --git a/http-api/tests/conftest.py b/http-api/tests/conftest.py index 834a221..ddab1ba 100644 --- a/http-api/tests/conftest.py +++ b/http-api/tests/conftest.py @@ -6,17 +6,21 @@ from http import HTTPMethod import jsonlines import pytest -from layercake.dynamodb import DynamoDBPersistenceLayer PYTEST_TABLE_NAME = 'pytest' -PK = os.getenv('DYNAMODB_PARTITION_KEY') -SK = os.getenv('DYNAMODB_SORT_KEY') +PK = 'id' +SK = 'sk' -patch = pytest.MonkeyPatch() -patch.setenv('USER_TABLE', PYTEST_TABLE_NAME) -patch.setenv('COURSE_TABLE', PYTEST_TABLE_NAME) -patch.setenv('ENROLLMENT_TABLE', PYTEST_TABLE_NAME) +# https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_configure +def pytest_configure(): + os.environ['TZ'] = 'America/Sao_Paulo' + os.environ['KONVIVA_API_URL'] = 'https://saladeaula.digital' + os.environ['DYNAMODB_PARTITION_KEY'] = PK + os.environ['DYNAMODB_SORT_KEY'] = SK + os.environ['USER_TABLE'] = PYTEST_TABLE_NAME + os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME + os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME @dataclass @@ -132,7 +136,9 @@ def dynamodb_client(): @pytest.fixture() -def dynamodb_persistence_layer(dynamodb_client) -> DynamoDBPersistenceLayer: +def dynamodb_persistence_layer(dynamodb_client): + from layercake.dynamodb import DynamoDBPersistenceLayer + return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client) diff --git a/http-api/uv.lock b/http-api/uv.lock index b10359e..d11f135 100644 --- a/http-api/uv.lock +++ b/http-api/uv.lock @@ -522,7 +522,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.2.16" +version = "0.3.0" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, diff --git a/layercake/layercake/dynamodb.py b/layercake/layercake/dynamodb.py index 89c5c9d..97107a6 100644 --- a/layercake/layercake/dynamodb.py +++ b/layercake/layercake/dynamodb.py @@ -146,11 +146,15 @@ if TYPE_CHECKING: Optional specification for nested data extraction. remove_prefix: str, optional Optional prefix to remove from the key when forming the result dict. + retain_key: bool, optional + Use the key itself as value if True; otherwise, use the extracted value. """ sk: str path_spec: str | None = None remove_prefix: str | None = None + retain_key: bool = False + else: class SortKey(str): @@ -166,6 +170,8 @@ else: Optional specification for nested data extraction. remove_prefix: str, optional Optional prefix to remove from the key when forming the result dict. + retain_key: bool, optional + Use the key itself as value if True; otherwise, use the extracted value. """ def __new__( @@ -174,6 +180,7 @@ else: *, path_spec: str | None = None, remove_prefix: str | None = None, + retain_key: bool = False, ) -> str: return super().__new__(cls, sk) @@ -183,12 +190,14 @@ else: *, path_spec: str | None = None, remove_prefix: str | None = None, + retain_key: bool = False, ) -> None: # __init__ is used to store the parameters for later reference. # For immutable types like str, __init__ cannot change the instance's value. self.sk = sk self.path_spec = path_spec self.remove_prefix = remove_prefix + self.retain_key = retain_key class Key(ABC, dict): @@ -929,29 +938,32 @@ class DynamoDBCollection: else: head, tail = {}, items - def _getin(pair: KeyPair, v: dict) -> dict: - v = omit((PK, SK), v) + def _getin(pair: KeyPair, obj: dict) -> dict: + obj = omit((PK, SK), obj) sk = pair[SK] path_spec = getattr(sk, 'path_spec', None) if path_spec: from glom import glom - return glom(v, path_spec) - return v + return glom(obj, path_spec) + return obj def _removeprefix(pair: KeyPair) -> str: + pk = pair[PK] sk = pair[SK] if not isinstance(sk, SortKey): - return pair[PK] + return pk - return sk.removeprefix(sk.remove_prefix or '') + key = pk if sk.retain_key else sk + + return key.removeprefix(sk.remove_prefix or '') return head | { - _removeprefix(pair): _getin(pair, item) - for pair, item in zip(sortkeys, tail) - if item + _removeprefix(pair): _getin(pair, obj) + for pair, obj in zip(sortkeys, tail) + if obj } def query( diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 050a53c..4ed00c5 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.3.0" +version = "0.3.1" description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." readme = "README.md" authors = [ diff --git a/layercake/tests/test_dynamodb.py b/layercake/tests/test_dynamodb.py index 427192d..152da24 100644 --- a/layercake/tests/test_dynamodb.py +++ b/layercake/tests/test_dynamodb.py @@ -3,7 +3,6 @@ from decimal import Decimal from ipaddress import IPv4Address import pytest - from layercake.dateutils import ttl from layercake.dynamodb import ( ComposeKey, @@ -371,3 +370,23 @@ def test_collection_get_items_pair_unflatten( 'cpf': {'user_id': '5OxmMjL-ujoR5IMGegQz'}, 'email': {'user_id': '5OxmMjL-ujoR5IMGegQz'}, } + + +def test_collection_get_items_pair_path_spec( + dynamodb_seeds, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, +): + collect = DynamoDBCollection(dynamodb_persistence_layer) + doc = collect.get_items( + KeyPair('cpf', SortKey('07879819908', path_spec='user_id', retain_key=True)) + + KeyPair( + 'email', + SortKey('osergiosiqueira@gmail.com', path_spec='user_id', retain_key=True), + ), + flatten_top=False, + ) + + assert doc == { + 'cpf': '5OxmMjL-ujoR5IMGegQz', + 'email': '5OxmMjL-ujoR5IMGegQz', + }