fix retain key

This commit is contained in:
2025-05-23 10:30:54 -03:00
parent 812470aae4
commit a7ee787378
26 changed files with 117 additions and 166 deletions

View File

@@ -2,7 +2,7 @@ version = 0.1
[default.deploy.parameters] [default.deploy.parameters]
stack_name = "saladeaula-batch-jobs" stack_name = "saladeaula-batch-jobs"
resolve_s3 = true resolve_s3 = true
s3_prefix = "batchjobs" s3_prefix = "batch_jobs"
region = "sa-east-1" region = "sa-east-1"
confirm_changeset = false confirm_changeset = false
capabilities = "CAPABILITY_IAM" capabilities = "CAPABILITY_IAM"

View File

@@ -9,7 +9,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:53 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:55
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -16,13 +16,11 @@
<h1>{{ name }}</h1> <h1>{{ name }}</h1>
<p> <p>
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
de <strong>{{ course }}</strong> com aproveitamento de de <strong>NR-10 Complementar (SEP)</strong> com aproveitamento
de
<strong>{{ progress }}%</strong> <strong>{{ progress }}%</strong>
</p> </p>
<p> <p>Realizado entre {{ start_date }} e {{ finish_date }}</p>
Realizado entre {{ start_date }} e {{ finish_date }}, com
validade até {{ due_date }}
</p>
<p>Florianópolis, SC, {{ today }}</p> <p>Florianópolis, SC, {{ today }}</p>
</section> </section>

View File

@@ -5,10 +5,6 @@
html, html,
body, body,
div, div,
span,
applet,
object,
iframe,
h1, h1,
h2, h2,
h3, h3,
@@ -16,73 +12,7 @@ h4,
h5, h5,
h6, h6,
p, p,
blockquote, a {
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 {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;

View File

@@ -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

View File

@@ -26,7 +26,6 @@ Example
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from typing import Any from typing import Any
import boto3
from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.utilities.data_classes import event_source from aws_lambda_powertools.utilities.data_classes import event_source
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import ( 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.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
from layercake.funcs import pick from layercake.funcs import pick
from boto3clients import dynamodb_client from boto3clients import dynamodb_client, idp_client
from cognito import get_user from cognito import get_user
from conf import USER_TABLE from conf import USER_TABLE
@@ -46,7 +45,6 @@ APIKEY_PREFIX = 'sk-'
tracer = Tracer() tracer = Tracer()
logger = Logger(__name__) logger = Logger(__name__)
idp_client = boto3.client('cognito-idp')
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer) user_collect = DynamoDBCollection(user_layer)

View File

@@ -2,15 +2,16 @@ import os
import boto3 import boto3
DYNAMODB_ENDPOINT_URL: str | None = None
# Only when running `sam local start-api` def get_dynamodb_client():
if 'AWS_SAM_LOCAL' in os.environ: sam_local = os.getenv('AWS_SAM_LOCAL')
DYNAMODB_ENDPOINT_URL = 'http://host.docker.internal:8000'
# Only when running `pytest` if os.getenv('AWS_LAMBDA_FUNCTION_NAME') and not sam_local:
if 'PYTEST_VERSION' in os.environ: return boto3.client('dynamodb')
DYNAMODB_ENDPOINT_URL = 'http://127.0.0.1:8000'
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') idp_client = boto3.client('cognito-idp')

View File

@@ -2,9 +2,9 @@ from dataclasses import asdict, dataclass
from urllib.parse import quote as urlquote from urllib.parse import quote as urlquote
from urllib.parse import urlencode, urlparse from urllib.parse import urlencode, urlparse
import requests
from aws_lambda_powertools.event_handler.exceptions import BadRequestError from aws_lambda_powertools.event_handler.exceptions import BadRequestError
from glom import glom from glom import glom
import requests
from conf import KONVIVA_API_URL, KONVIVA_SECRET_KEY from conf import KONVIVA_API_URL, KONVIVA_SECRET_KEY

View File

@@ -119,12 +119,12 @@ def _tenant(
# Ensure user has ACL # Ensure user has ACL
collect.get_item( collect.get_item(
KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')), KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')),
exception_cls=ForbiddenError, exc_cls=ForbiddenError,
) )
# For root tenant, provide the default Tenant # For root tenant, provide the default Tenant
if tenant_id == '*': if tenant_id == '*':
return Tenant(id=tenant_id, name='default') return Tenant(id=tenant_id, name='default')
obj = collect.get_item(KeyPair(tenant_id, '0'), exception_cls=NotFoundError) obj = collect.get_item(KeyPair(tenant_id, '0'), exc_cls=NotFoundError)
return Tenant.parse_obj(obj) return Tenant.model_validate(obj)

View File

@@ -76,7 +76,7 @@ def post_course(payload: Course):
def get_course(id: str): def get_course(id: str):
return course_collect.get_item( return course_collect.get_item(
KeyPair(id, '0'), KeyPair(id, '0'),
exception_cls=NotFoundError, exc_cls=NotFoundError,
) )

View File

@@ -17,7 +17,7 @@ from conf import ELASTIC_CONN, ORDER_TABLE
router = Router() router = Router()
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) 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) elastic_client = Elasticsearch(**ELASTIC_CONN)

View File

@@ -20,7 +20,7 @@ from rules.org import update_policies
router = Router() router = Router()
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) 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( @router.get(
@@ -31,7 +31,9 @@ org_collect = DynamoDBCollection(org_layer, exception_cls=BadRequestError)
) )
def get_policies(id: str): def get_policies(id: str):
return org_collect.get_items( 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, flatten_top=False,
) )

View File

@@ -39,7 +39,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError):
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) 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) elastic_client = Elasticsearch(**ELASTIC_CONN)

View File

@@ -25,7 +25,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) 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( @router.get(

View File

@@ -23,7 +23,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) 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( @router.get(

View File

@@ -26,7 +26,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) 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( @router.get(

View File

@@ -23,7 +23,7 @@ def create_course(
transact.put( transact.put(
item={ item={
'id': course.id, 'id': course.id,
'sk': 'tenant', 'sk': 'metadata#tenant',
'org_id': org.id, 'org_id': org.id,
'name': org.name, 'name': org.name,
'create_date': now_, 'create_date': now_,
@@ -42,7 +42,8 @@ def update_course(
transact = TransactItems(persistence_layer.table_name) transact = TransactItems(persistence_layer.table_name)
transact.update( transact.update(
key=KeyPair(id, '0'), 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={ expr_attr_names={
'#name': 'name', '#name': 'name',
}, },

View File

@@ -17,24 +17,24 @@ def update_policies(
transact.put( transact.put(
item={ item={
'id': id, 'id': id,
'sk': 'payment_policy', 'sk': 'metadata#payment_policy',
'create_date': now_, 'create_date': now_,
} }
| payment_policy | payment_policy
) )
else: else:
transact.delete(key=KeyPair(id, 'payment_policy')) transact.delete(key=KeyPair(id, 'metadata#payment_policy'))
if billing_policy: if billing_policy:
transact.put( transact.put(
item={ item={
'id': id, 'id': id,
'sk': 'billing_policy', 'sk': 'metadata#billing_policy',
'create_date': now_, 'create_date': now_,
} }
| billing_policy | billing_policy
) )
else: else:
transact.delete(key=KeyPair(id, 'billing_policy')) transact.delete(key=KeyPair(id, 'metadata#billing_policy'))
return persistence_layer.transact_write_items(transact) return persistence_layer.transact_write_items(transact)

View File

@@ -4,7 +4,6 @@ from typing import TypedDict
from aws_lambda_powertools.event_handler.exceptions import ( from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError, BadRequestError,
) )
from botocore.exceptions import ClientError
from botocore.tokens import timedelta from botocore.tokens import timedelta
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import ( from layercake.dynamodb import (
@@ -14,11 +13,6 @@ from layercake.dynamodb import (
TransactItems, TransactItems,
) )
class CPFConflictError(BadRequestError):
pass
User = TypedDict('User', {'id': str, 'name': str, 'cpf': str}) User = TypedDict('User', {'id': str, 'name': str, 'cpf': str})
@@ -60,6 +54,10 @@ def update_user(
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
) )
class CPFConflictError(BadRequestError):
def __init__(self, msg: str):
super().__init__('Cpf already exists')
if user.cpf != old_cpf: if user.cpf != old_cpf:
transact.put( transact.put(
item={ item={
@@ -69,18 +67,14 @@ def update_user(
'create_date': now_, 'create_date': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
exc_cls=CPFConflictError,
) )
# Ensures that the old CPF is discarded # Ensures that the old CPF is discarded
if old_cpf: if old_cpf:
transact.delete(key=KeyPair('cpf', old_cpf)) transact.delete(key=KeyPair('cpf', old_cpf))
try: return persistence_layer.transact_write_items(transact)
persistence_layer.transact_write_items(transact)
except ClientError:
raise CPFConflictError('CPF is already in use.')
else:
return True
def add_email( def add_email(
@@ -109,6 +103,11 @@ def add_email(
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
) )
class EmailConflictError(BadRequestError):
def __init__(self, msg: str):
super().__init__('Email already exists')
transact.put( transact.put(
item={ item={
'id': 'email', 'id': 'email',
@@ -117,12 +116,10 @@ def add_email(
'create_date': now_, 'create_date': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
exc_cls=EmailConflictError,
) )
try:
return persistence_layer.transact_write_items(transact) return persistence_layer.transact_write_items(transact)
except ClientError:
raise BadRequestError('Email already exists.')
def del_email( def del_email(
@@ -141,6 +138,7 @@ def del_email(
key=KeyPair(id, ComposeKey(email, prefix='emails')), key=KeyPair(id, ComposeKey(email, prefix='emails')),
cond_expr='email_primary <> :primary', cond_expr='email_primary <> :primary',
expr_attr_values={':primary': True}, expr_attr_values={':primary': True},
exc_cls=BadRequestError,
) )
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
@@ -150,10 +148,7 @@ def del_email(
}, },
) )
try:
return persistence_layer.transact_write_items(transact) return persistence_layer.transact_write_items(transact)
except ClientError:
raise BadRequestError('Cannot remove the primary email.')
def set_email_as_primary( def set_email_as_primary(
@@ -188,10 +183,8 @@ def set_email_as_primary(
) )
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr=( update_expr='SET email = :email, email_verified = :email_verified, \
'SET email = :email, email_verified = :email_verified, ' update_date = :update_date',
'update_date = :update_date'
),
expr_attr_values={ expr_attr_values={
':email': new_email, ':email': new_email,
':email_verified': email_verified, ':email_verified': email_verified,

View File

@@ -1,9 +1,9 @@
{ {
"Parameters": { "Parameters": {
"KONVIVA_API_KEY": "", "KONVIVA_API_KEY": "",
"USER_TABLE": "test-users", "USER_TABLE": "",
"ORDER_TABLE": "test-orders", "ORDER_TABLE": "",
"ENROLLMENT_TABLE": "test-enrollments" "ENROLLMENT_TABLE": ""
"COURSE_TABLE": "test-courses" "COURSE_TABLE": ""
} }
} }

View File

@@ -23,7 +23,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:48 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:59
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo
@@ -39,7 +39,7 @@ Globals:
ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}" ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}"
KONVIVA_API_URL: https://saladeaula.digital KONVIVA_API_URL: https://saladeaula.digital
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}" 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}}" MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}"
Resources: Resources:

View File

@@ -6,17 +6,21 @@ from http import HTTPMethod
import jsonlines import jsonlines
import pytest import pytest
from layercake.dynamodb import DynamoDBPersistenceLayer
PYTEST_TABLE_NAME = 'pytest' PYTEST_TABLE_NAME = 'pytest'
PK = os.getenv('DYNAMODB_PARTITION_KEY') PK = 'id'
SK = os.getenv('DYNAMODB_SORT_KEY') SK = 'sk'
patch = pytest.MonkeyPatch() # https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_configure
patch.setenv('USER_TABLE', PYTEST_TABLE_NAME) def pytest_configure():
patch.setenv('COURSE_TABLE', PYTEST_TABLE_NAME) os.environ['TZ'] = 'America/Sao_Paulo'
patch.setenv('ENROLLMENT_TABLE', PYTEST_TABLE_NAME) 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 @dataclass
@@ -132,7 +136,9 @@ def dynamodb_client():
@pytest.fixture() @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) return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client)

2
http-api/uv.lock generated
View File

@@ -522,7 +522,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.2.16" version = "0.3.0"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },

View File

@@ -146,11 +146,15 @@ if TYPE_CHECKING:
Optional specification for nested data extraction. Optional specification for nested data extraction.
remove_prefix: str, optional remove_prefix: str, optional
Optional prefix to remove from the key when forming the result dict. 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 sk: str
path_spec: str | None = None path_spec: str | None = None
remove_prefix: str | None = None remove_prefix: str | None = None
retain_key: bool = False
else: else:
class SortKey(str): class SortKey(str):
@@ -166,6 +170,8 @@ else:
Optional specification for nested data extraction. Optional specification for nested data extraction.
remove_prefix: str, optional remove_prefix: str, optional
Optional prefix to remove from the key when forming the result dict. 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__( def __new__(
@@ -174,6 +180,7 @@ else:
*, *,
path_spec: str | None = None, path_spec: str | None = None,
remove_prefix: str | None = None, remove_prefix: str | None = None,
retain_key: bool = False,
) -> str: ) -> str:
return super().__new__(cls, sk) return super().__new__(cls, sk)
@@ -183,12 +190,14 @@ else:
*, *,
path_spec: str | None = None, path_spec: str | None = None,
remove_prefix: str | None = None, remove_prefix: str | None = None,
retain_key: bool = False,
) -> None: ) -> None:
# __init__ is used to store the parameters for later reference. # __init__ is used to store the parameters for later reference.
# For immutable types like str, __init__ cannot change the instance's value. # For immutable types like str, __init__ cannot change the instance's value.
self.sk = sk self.sk = sk
self.path_spec = path_spec self.path_spec = path_spec
self.remove_prefix = remove_prefix self.remove_prefix = remove_prefix
self.retain_key = retain_key
class Key(ABC, dict): class Key(ABC, dict):
@@ -929,29 +938,32 @@ class DynamoDBCollection:
else: else:
head, tail = {}, items head, tail = {}, items
def _getin(pair: KeyPair, v: dict) -> dict: def _getin(pair: KeyPair, obj: dict) -> dict:
v = omit((PK, SK), v) obj = omit((PK, SK), obj)
sk = pair[SK] sk = pair[SK]
path_spec = getattr(sk, 'path_spec', None) path_spec = getattr(sk, 'path_spec', None)
if path_spec: if path_spec:
from glom import glom from glom import glom
return glom(v, path_spec) return glom(obj, path_spec)
return v return obj
def _removeprefix(pair: KeyPair) -> str: def _removeprefix(pair: KeyPair) -> str:
pk = pair[PK]
sk = pair[SK] sk = pair[SK]
if not isinstance(sk, SortKey): 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 | { return head | {
_removeprefix(pair): _getin(pair, item) _removeprefix(pair): _getin(pair, obj)
for pair, item in zip(sortkeys, tail) for pair, obj in zip(sortkeys, tail)
if item if obj
} }
def query( def query(

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "layercake" name = "layercake"
version = "0.3.0" version = "0.3.1"
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
readme = "README.md" readme = "README.md"
authors = [ authors = [

View File

@@ -3,7 +3,6 @@ from decimal import Decimal
from ipaddress import IPv4Address from ipaddress import IPv4Address
import pytest import pytest
from layercake.dateutils import ttl from layercake.dateutils import ttl
from layercake.dynamodb import ( from layercake.dynamodb import (
ComposeKey, ComposeKey,
@@ -371,3 +370,23 @@ def test_collection_get_items_pair_unflatten(
'cpf': {'user_id': '5OxmMjL-ujoR5IMGegQz'}, 'cpf': {'user_id': '5OxmMjL-ujoR5IMGegQz'},
'email': {'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',
}