update layercake version

This commit is contained in:
2025-05-28 17:52:15 -03:00
parent 42e62ec183
commit 797a325cb0
28 changed files with 692 additions and 566 deletions

View File

@@ -5,7 +5,7 @@ from aws_lambda_powertools.utilities.data_classes import (
) )
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dateutils import now from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE from config import ENROLLMENT_TABLE
@@ -19,7 +19,8 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] new_image = event.detail['new_image']
now_ = now() now_ = now()
transact = TransactItems(enrollment_layer.table_name)
with enrollment_layer.transact_items() as transact:
transact.update( transact.update(
key=KeyPair(new_image['id'], '0'), key=KeyPair(new_image['id'], '0'),
update_expr='SET #status = :archived, update_date = :update_date', update_expr='SET #status = :archived, update_date = :update_date',
@@ -40,7 +41,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'create_date': now_, 'create_date': now_,
}, },
) )
transact.write_items()
enrollment_layer.transact_write_items(transact)
return True return True

View File

@@ -26,7 +26,7 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] new_image = event.detail['new_image']
order_id = new_image['id'] order_id = new_image['id']
data = order_layer.collect.get_items( data = order_layer.collection.get_items(
TransactKey(order_id) TransactKey(order_id)
+ SortKey('0') + SortKey('0')
+ KeyPair( + KeyPair(
@@ -42,7 +42,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
total = data['total'] total = data['total']
tenant_id = data['tenant_id'].removeprefix('ORG#') tenant_id = data['tenant_id'].removeprefix('ORG#')
policy = user_layer.collect.get_item( policy = user_layer.collection.get_item(
KeyPair(pk=tenant_id, sk='metadata#billing_policy'), KeyPair(pk=tenant_id, sk='metadata#billing_policy'),
raise_on_error=False, raise_on_error=False,
default=False, default=False,
@@ -51,7 +51,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
if not policy or total <= 0: if not policy or total <= 0:
return False return False
result = enrollment_layer.collect.query( result = enrollment_layer.collection.query(
KeyPair( KeyPair(
ComposeKey(tenant_id, prefix='vacancies'), ComposeKey(tenant_id, prefix='vacancies'),
order_id, order_id,

View File

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

View File

@@ -23,7 +23,7 @@ def test_del_vacancies(
} }
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore
result = dynamodb_persistence_layer.collect.query( result = dynamodb_persistence_layer.collection.query(
PartitionKey('vacancies#cJtK9SsnJhKPyxESe7g3DG') PartitionKey('vacancies#cJtK9SsnJhKPyxESe7g3DG')
) )

View File

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

36
http-api/app/config.py Normal file
View File

@@ -0,0 +1,36 @@
import os
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
KONVIVA_API_URL: str = os.getenv('KONVIVA_API_URL') # type: ignore
KONVIVA_SECRET_KEY: str = os.getenv('KONVIVA_SECRET_KEY') # type: ignore
MEILISEARCH_HOST: str = os.getenv('MEILISEARCH_HOST') # type: ignore
MEILISEARCH_API_KEY: str = os.getenv('MEILISEARCH_API_KEY') # type: ignore
match os.getenv('AWS_SAM_LOCAL'), os.getenv('PYTEST_VERSION'):
case str() as SAM_LOCAL, _ if SAM_LOCAL: # Only when running `sam local start-api`
MEILISEARCH_HOST = 'http://host.docker.internal:7700'
ELASTIC_CONN = {
'hosts': 'http://host.docker.internal:9200',
}
case _, str() as PYTEST if PYTEST: # Only when running `pytest`
MEILISEARCH_HOST = 'http://127.0.0.1:7700'
ELASTIC_CONN = {
'hosts': 'http://127.0.0.1:9200',
}
case _:
MEILISEARCH_HOST: str = os.getenv('MEILISEARCH_HOST') # type: ignore
ELASTIC_CLOUD_ID = os.getenv('ELASTIC_CLOUD_ID')
ELASTIC_AUTH_PASS = os.getenv('ELASTIC_AUTH_PASS')
ELASTIC_CONN = {
'cloud_id': ELASTIC_CLOUD_ID,
'basic_auth': ('elastic', ELASTIC_AUTH_PASS),
}
USER_POOOL_ID = 'sa-east-1_s6YmVSfXj'

View File

@@ -45,12 +45,12 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
self, self,
action: str, action: str,
/, /,
collect: DynamoDBCollection, collection: DynamoDBCollection,
audit_attrs: tuple[str, ...] = (), audit_attrs: tuple[str, ...] = (),
retention_days: int | None = LOG_RETENTION_DAYS, retention_days: int | None = LOG_RETENTION_DAYS,
) -> None: ) -> None:
self.action = action self.action = action
self.collect = collect self.collection = collection
self.audit_attrs = audit_attrs self.audit_attrs = audit_attrs
self.retention_days = retention_days self.retention_days = retention_days
@@ -80,7 +80,7 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
else None else None
) )
self.collect.put_item( self.collection.put_item(
key=KeyPair( key=KeyPair(
# Post-migration: remove `delimiter` and update prefix # Post-migration: remove `delimiter` and update prefix
# from `log` to `logs` in ComposeKey. # from `log` to `logs` in ComposeKey.

View File

@@ -46,11 +46,11 @@ class TenantMiddleware(BaseMiddlewareHandler):
def __init__( def __init__(
self, self,
collect: DynamoDBCollection, collection: DynamoDBCollection,
/, /,
header: str = 'X-Tenant', header: str = 'X-Tenant',
) -> None: ) -> None:
self.collect = collect self.collection = collection
self.header = header self.header = header
def handler( def handler(
@@ -69,7 +69,7 @@ class TenantMiddleware(BaseMiddlewareHandler):
tenant=_tenant( tenant=_tenant(
app.current_event.headers.get(self.header), app.current_event.headers.get(self.header),
app.context.get('user'), # type: ignore app.context.get('user'), # type: ignore
collect=self.collect, collection=self.collection,
) )
) )
@@ -85,7 +85,7 @@ def _tenant(
tenant_id: str | None, tenant_id: str | None,
user: User, user: User,
/, /,
collect: DynamoDBCollection, collection: DynamoDBCollection,
) -> Tenant: ) -> Tenant:
"""Get a Tenant instance based on the provided tenant_id """Get a Tenant instance based on the provided tenant_id
and user's access permissions. and user's access permissions.
@@ -96,7 +96,7 @@ def _tenant(
The identifier of the tenant. Must not be None or empty. The identifier of the tenant. Must not be None or empty.
user : User user : User
The user attempting to access the tenant. The user attempting to access the tenant.
collect : DynamoDBCollection collection : DynamoDBCollection
The DynamoDB collection used to retrieve tenant information. The DynamoDB collection used to retrieve tenant information.
Returns Returns
@@ -117,7 +117,7 @@ def _tenant(
raise BadRequestError('Missing tenant') raise BadRequestError('Missing tenant')
# Ensure user has ACL # Ensure user has ACL
collect.get_item( collection.get_item(
KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')), KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')),
exc_cls=ForbiddenError, exc_cls=ForbiddenError,
) )
@@ -126,5 +126,5 @@ def _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'), exc_cls=NotFoundError) obj = collection.get_item(KeyPair(tenant_id, '0'), exc_cls=NotFoundError)
return Tenant.model_validate(obj) return Tenant.model_validate(obj)

View File

@@ -22,8 +22,6 @@ router = Router()
meili_client = Meilisearch(MEILISEARCH_HOST, MEILISEARCH_API_KEY) meili_client = Meilisearch(MEILISEARCH_HOST, MEILISEARCH_API_KEY)
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer)
course_collect = DynamoDBCollection(course_layer)
@router.get( @router.get(
@@ -55,8 +53,8 @@ def get_courses():
compress=True, compress=True,
tags=['Course'], tags=['Course'],
middlewares=[ middlewares=[
TenantMiddleware(user_collect), TenantMiddleware(user_layer.collection),
AuditLogMiddleware('COURSE_ADD', user_collect, ('id', 'name')), AuditLogMiddleware('COURSE_ADD', user_layer.collection, ('id', 'name')),
], ],
) )
def post_course(payload: Course): def post_course(payload: Course):
@@ -74,7 +72,7 @@ def post_course(payload: Course):
@router.get('/<id>', compress=True, tags=['Course']) @router.get('/<id>', compress=True, tags=['Course'])
def get_course(id: str): def get_course(id: str):
return course_collect.get_item( return course_layer.collection.get_item(
KeyPair(id, '0'), KeyPair(id, '0'),
exc_cls=NotFoundError, exc_cls=NotFoundError,
) )
@@ -85,8 +83,8 @@ def get_course(id: str):
compress=True, compress=True,
tags=['Course'], tags=['Course'],
middlewares=[ middlewares=[
TenantMiddleware(user_collect), TenantMiddleware(user_layer.collection),
AuditLogMiddleware('COURSE_UPDATE', user_collect, ('id', 'name')), AuditLogMiddleware('COURSE_UPDATE', user_layer.collection, ('id', 'name')),
], ],
) )
def put_course(id: str, payload: Course): def put_course(id: str, payload: Course):

View File

@@ -0,0 +1,55 @@
from aws_lambda_powertools.event_handler.api_gateway import Router
from elasticsearch import Elasticsearch
from layercake.dynamodb import (
DynamoDBCollection,
DynamoDBPersistenceLayer,
KeyPair,
)
from pydantic import UUID4, BaseModel
from boto3clients import dynamodb_client
from config import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE
from middlewares.audit_log_middleware import AuditLogMiddleware
from middlewares.authentication_middleware import User
from rules.enrollment import set_status_as_canceled
from .vacancies import router as vacancies
__all__ = ['vacancies']
router = Router()
elastic_client = Elasticsearch(**ELASTIC_CONN)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer)
class Cancel(BaseModel):
id: UUID4 | str
lock_hash: str
course: dict = {}
vacancy: dict = {}
@router.patch(
'/<id>/cancel',
compress=True,
tags=['Enrollment'],
middlewares=[
AuditLogMiddleware('ENROLLMENT_CANCEL', user_collect, ('id', 'course'))
],
)
def cancel(id: str, payload: Cancel):
user: User = router.context['user']
set_status_as_canceled(
id,
lock_hash=payload.lock_hash,
author=user.model_dump(), # type: ignore
course=payload.course, # type: ignore
vacancy_key=KeyPair.parse_obj(payload.vacancy),
persistence_layer=enrollment_layer,
)
return payload

View File

@@ -0,0 +1,64 @@
from datetime import datetime
from aws_lambda_powertools.event_handler.api_gateway import Router
from layercake.batch import BatchProcessor
from layercake.dynamodb import (
DynamoDBCollection,
DynamoDBPersistenceLayer,
)
from pydantic import BaseModel
from boto3clients import dynamodb_client
from config import (
ENROLLMENT_TABLE,
USER_TABLE,
)
from middlewares import Tenant, TenantMiddleware
from models import Course, User
router = Router()
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer)
enrollment_collect = DynamoDBCollection(enrollment_layer)
processor = BatchProcessor()
class Item(BaseModel):
user: User
course: Course
schedule_date: datetime | None = None
class Payload(BaseModel):
items: tuple[Item, ...]
@router.post(
'/',
compress=True,
tags=['Enrollment'],
middlewares=[
TenantMiddleware(user_collect),
],
)
def enroll_(payload: Payload):
context = {'tenant': router.context['tenant']}
with processor(payload.items, handler, context):
processor.process()
return {}
def handler(record: Item, context: dict):
tenant: Tenant = context['tenant']
# enroll(
# enrollment=Enrollment(user=[])
# tenant={
# 'id': str(tenant.id),
# 'name': tenant.name,
# },
# persistence_layer=enrollment_layer,
# )

View File

@@ -1,6 +1,5 @@
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
PrefixKey, PrefixKey,
@@ -13,7 +12,6 @@ from middlewares import User
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer)
LIMIT = 25 LIMIT = 25
@@ -22,11 +20,11 @@ LIMIT = 25
@router.get('/', include_in_schema=False) @router.get('/', include_in_schema=False)
def settings(): def settings():
user: User = router.context['user'] user: User = router.context['user']
acls = user_collect.query( acls = user_layer.collection.query(
KeyPair(user.id, PrefixKey('acls')), KeyPair(user.id, PrefixKey('acls')),
limit=LIMIT, limit=LIMIT,
) )
tenants = user_collect.query( tenants = user_layer.collection.query(
KeyPair(user.id, PrefixKey('orgs')), KeyPair(user.id, PrefixKey('orgs')),
limit=LIMIT, limit=LIMIT,
) )

View File

@@ -5,7 +5,6 @@ from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError as PowertoolsBadRequestError, BadRequestError as PowertoolsBadRequestError,
) )
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
MissingError, MissingError,
@@ -26,7 +25,6 @@ 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, exc_cls=BadRequestError)
@router.get( @router.get(
@@ -36,7 +34,7 @@ user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
summary='Get user orgs', summary='Get user orgs',
) )
def get_orgs(id: str): def get_orgs(id: str):
return user_collect.query( return user_layer.collection.query(
KeyPair(id, PrefixKey('orgs')), KeyPair(id, PrefixKey('orgs')),
start_key=router.current_event.get_query_string_value('start_key', None), start_key=router.current_event.get_query_string_value('start_key', None),
) )
@@ -54,7 +52,9 @@ class Unassign(BaseModel):
tags=['User'], tags=['User'],
summary='Delete user org', summary='Delete user org',
middlewares=[ middlewares=[
AuditLogMiddleware('UNASSIGN_ORG', user_collect, ('id', 'name', 'cnpj')) AuditLogMiddleware(
'UNASSIGN_ORG', user_layer.collection, ('id', 'name', 'cnpj')
)
], ],
) )
def delete_org(id: str, payload: Unassign): def delete_org(id: str, payload: Unassign):

View File

@@ -1,5 +1,5 @@
from layercake.dateutils import now from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from models import Course, Org from models import Course, Org
@@ -11,7 +11,7 @@ def create_course(
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
): ):
now_ = now() now_ = now()
transact = TransactItems(persistence_layer.table_name) with persistence_layer.transact_writer() as transact:
transact.put( transact.put(
item={ item={
'sk': '0', 'sk': '0',
@@ -29,7 +29,8 @@ def create_course(
'create_date': now_, 'create_date': now_,
} }
) )
return persistence_layer.transact_write_items(transact)
return True
def update_course( def update_course(
@@ -39,7 +40,7 @@ def update_course(
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
): ):
now_ = now() now_ = now()
transact = TransactItems(persistence_layer.table_name) with persistence_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr='SET #name = :name, access_period = :access_period, \ update_expr='SET #name = :name, access_period = :access_period, \
@@ -55,4 +56,4 @@ def update_course(
}, },
cond_expr='attribute_exists(sk)', cond_expr='attribute_exists(sk)',
) )
return persistence_layer.transact_write_items(transact) return True

View File

@@ -4,7 +4,7 @@ from typing import TypedDict
from uuid import uuid4 from uuid import uuid4
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from layercake.strutils import md5_hash from layercake.strutils import md5_hash
from config import ORDER_TABLE from config import ORDER_TABLE
@@ -64,7 +64,7 @@ def enroll(
course = enrollment.course course = enrollment.course
tenant_id = tenant['id'] tenant_id = tenant['id']
transact = TransactItems(persistence_layer.table_name) with persistence_layer.transact_writer() as transact:
transact.put( transact.put(
item={ item={
'sk': '0', 'sk': '0',
@@ -154,7 +154,7 @@ def enroll(
}, },
) )
return persistence_layer.transact_write_items(transact) return True
def set_status_as_canceled( def set_status_as_canceled(
@@ -169,7 +169,8 @@ def set_status_as_canceled(
"""Cancel the enrollment if there's a `cancel_policy` """Cancel the enrollment if there's a `cancel_policy`
and put its vacancy back if `vacancy_key` is provided.""" and put its vacancy back if `vacancy_key` is provided."""
now_ = now() now_ = now()
transact = TransactItems(persistence_layer.table_name)
with persistence_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr='SET #status = :canceled, update_date = :update', update_expr='SET #status = :canceled, update_date = :update',
@@ -238,4 +239,4 @@ def set_status_as_canceled(
table_name=ORDER_TABLE, table_name=ORDER_TABLE,
) )
return persistence_layer.transact_write_items(transact) return True

View File

@@ -1,5 +1,5 @@
from layercake.dateutils import now from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
def update_policies( def update_policies(
@@ -11,8 +11,8 @@ def update_policies(
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
): ):
now_ = now() now_ = now()
transact = TransactItems(persistence_layer.table_name)
with persistence_layer.transact_writer() as transact:
if payment_policy: if payment_policy:
transact.put( transact.put(
item={ item={
@@ -37,4 +37,4 @@ def update_policies(
else: else:
transact.delete(key=KeyPair(id, 'metadata#billing_policy')) transact.delete(key=KeyPair(id, 'metadata#billing_policy'))
return persistence_layer.transact_write_items(transact) return True

View File

@@ -10,25 +10,23 @@ from layercake.dynamodb import (
ComposeKey, ComposeKey,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
TransactItems,
) )
User = TypedDict('User', {'id': str, 'name': str, 'cpf': str}) User = TypedDict('User', {'id': str, 'name': str, 'cpf': str})
def update_user( def update_user(
userdata: User, data: User,
/, /,
*, *,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
) -> bool: ) -> bool:
now_ = now() now_ = now()
ttl_ = now_ + timedelta(hours=24) user = SimpleNamespace(**data)
user = SimpleNamespace(**userdata)
# Get the user's CPF, if it exists. # Get the user's CPF, if it exists.
old_cpf = persistence_layer.get_item(KeyPair(user.id, '0')).get('cpf', None) old_cpf = persistence_layer.get_item(KeyPair(user.id, '0')).get('cpf', None)
transact = TransactItems(persistence_layer.table_name) with persistence_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(user.id, '0'), key=KeyPair(user.id, '0'),
update_expr='SET #name = :name, cpf = :cpf, update_date = :update_date', update_expr='SET #name = :name, cpf = :cpf, update_date = :update_date',
@@ -48,8 +46,7 @@ def update_user(
'id': user.id, 'id': user.id,
'sk': 'last_profile_edit', 'sk': 'last_profile_edit',
'create_date': now_, 'create_date': now_,
'ttl': ttl(start_dt=ttl_), 'ttl': ttl(start_dt=now_ + timedelta(hours=24)),
'ttl_date': ttl_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
) )
@@ -74,7 +71,7 @@ def update_user(
if old_cpf: if old_cpf:
transact.delete(key=KeyPair('cpf', old_cpf)) transact.delete(key=KeyPair('cpf', old_cpf))
return persistence_layer.transact_write_items(transact) return True
def add_email( def add_email(
@@ -85,7 +82,8 @@ def add_email(
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
): ):
now_ = now() now_ = now()
transact = TransactItems(persistence_layer.table_name)
with persistence_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr='ADD emails :email', update_expr='ADD emails :email',
@@ -119,7 +117,7 @@ def add_email(
exc_cls=EmailConflictError, exc_cls=EmailConflictError,
) )
return persistence_layer.transact_write_items(transact) return True
def del_email( def del_email(
@@ -130,7 +128,7 @@ def del_email(
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
) -> bool: ) -> bool:
"""Delete any email except the primary email.""" """Delete any email except the primary email."""
transact = TransactItems(persistence_layer.table_name) with persistence_layer.transact_writer() as transact:
transact.delete( transact.delete(
key=KeyPair('email', email), key=KeyPair('email', email),
) )
@@ -147,8 +145,7 @@ def del_email(
':email': {email}, ':email': {email},
}, },
) )
return True
return persistence_layer.transact_write_items(transact)
def set_email_as_primary( def set_email_as_primary(
@@ -162,7 +159,8 @@ def set_email_as_primary(
): ):
now_ = now() now_ = now()
expr = 'SET email_primary = :email_primary, update_date = :update_date' expr = 'SET email_primary = :email_primary, update_date = :update_date'
transact = TransactItems(persistence_layer.table_name)
with persistence_layer.transact_writer() as transact:
# Set the old email as non-primary # Set the old email as non-primary
transact.update( transact.update(
key=KeyPair(id, ComposeKey(old_email, 'emails')), key=KeyPair(id, ComposeKey(old_email, 'emails')),
@@ -192,7 +190,7 @@ def set_email_as_primary(
}, },
) )
return persistence_layer.transact_write_items(transact) return True
def del_org_member( def del_org_member(
@@ -201,8 +199,7 @@ def del_org_member(
org_id: str, org_id: str,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
) -> bool: ) -> bool:
transact = TransactItems(persistence_layer.table_name) with persistence_layer.transact_writer() as transact:
# Remove the user's relationship with the organization and their privileges # Remove the user's relationship with the organization and their privileges
transact.delete(key=KeyPair(id, f'acls#{org_id}')) transact.delete(key=KeyPair(id, f'acls#{org_id}'))
transact.delete(key=KeyPair(id, f'orgs#{org_id}')) transact.delete(key=KeyPair(id, f'orgs#{org_id}'))
@@ -217,4 +214,4 @@ def del_org_member(
transact.delete(key=KeyPair(org_id, f'admins#{id}')) transact.delete(key=KeyPair(org_id, f'admins#{id}'))
transact.delete(key=KeyPair(f'orgmembers#{org_id}', id)) transact.delete(key=KeyPair(f'orgmembers#{org_id}', id))
return persistence_layer.transact_write_items(transact) return True

View File

@@ -3,7 +3,6 @@ from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import ( from layercake.dynamodb import (
ComposeKey, ComposeKey,
DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
PartitionKey, PartitionKey,
@@ -58,8 +57,8 @@ def test_post_course(
assert 'id' in json.loads(r['body']) assert 'id' in json.loads(r['body'])
assert r['statusCode'] == HTTPStatus.CREATED assert r['statusCode'] == HTTPStatus.CREATED
collect = DynamoDBCollection(dynamodb_persistence_layer) collection = dynamodb_persistence_layer.collection
logs = collect.query( logs = collection.query(
PartitionKey( PartitionKey(
ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='log', delimiter=':'), ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='log', delimiter=':'),
) )
@@ -92,6 +91,6 @@ def test_put_course(
) )
assert r['statusCode'] == HTTPStatus.OK assert r['statusCode'] == HTTPStatus.OK
collect = DynamoDBCollection(dynamodb_persistence_layer) collection = dynamodb_persistence_layer.collection
course = collect.get_item(KeyPair('90d7f0d2-d9a4-4467-a31c-f9a7955964cf', '0')) course = collection.get_item(KeyPair('90d7f0d2-d9a4-4467-a31c-f9a7955964cf', '0'))
assert course['name'] == 'pytest' assert course['name'] == 'pytest'

2
http-api/uv.lock generated
View File

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

View File

@@ -339,10 +339,10 @@ class TransactionCanceledException(Exception):
class TransactOperation: class TransactOperation:
def __init__( def __init__(
self, self,
op: dict, operation: dict,
exc_cls: type[Exception] | None = None, exc_cls: type[Exception] | None = None,
) -> None: ) -> None:
self.op = op self.operation = operation
self.exc_cls = exc_cls self.exc_cls = exc_cls
@@ -352,23 +352,27 @@ else:
DynamoDBClient = object DynamoDBClient = object
class TransactItems: class TransactWriter:
def __init__( def __init__(
self, self,
table_name: str, table_name: str,
*,
flush_amount: int,
client: DynamoDBClient, client: DynamoDBClient,
) -> None: ) -> None:
self._table_name = table_name self._table_name = table_name
self._operations: list[TransactOperation] = [] self._items_buffer: list[TransactOperation] = []
self._flush_amount = flush_amount
self._client = client self._client = client
def __enter__(self) -> Self: def __enter__(self) -> Self:
"""Remove operations from previous execution."""
self._operations.clear()
return self return self
def __exit__(self, exc_type, exc_val, exc_tb) -> bool: def __exit__(self, *exc_details) -> None:
return False # When we exit, we need to keep flushing whatever's left
# until there's nothing left in our items buffer.
while self._items_buffer:
self._flush()
def put( def put(
self, self,
@@ -386,7 +390,7 @@ class TransactItems:
if not table_name: if not table_name:
table_name = self._table_name table_name = self._table_name
self._operations.append( self._add_op_and_process(
TransactOperation( TransactOperation(
{ {
'Put': dict( 'Put': dict(
@@ -406,8 +410,8 @@ class TransactItems:
update_expr: str, update_expr: str,
cond_expr: str | None = None, cond_expr: str | None = None,
table_name: str | None = None, table_name: str | None = None,
expr_attr_names: dict = {}, expr_attr_names: dict | None = None,
expr_attr_values: dict = {}, expr_attr_values: dict | None = None,
exc_cls: Type[Exception] | None = None, exc_cls: Type[Exception] | None = None,
) -> None: ) -> None:
attrs: dict = {} attrs: dict = {}
@@ -424,7 +428,7 @@ class TransactItems:
if not table_name: if not table_name:
table_name = self._table_name table_name = self._table_name
self._operations.append( self._add_op_and_process(
TransactOperation( TransactOperation(
{ {
'Update': dict( 'Update': dict(
@@ -438,41 +442,14 @@ class TransactItems:
) )
) )
def get(
self,
*,
table_name: str | None = None,
key: dict,
expr_attr_names: str | None = None,
) -> None:
attrs: dict = {}
if expr_attr_names:
attrs['ExpressionAttributeNames'] = expr_attr_names
if not table_name:
table_name = self._table_name
self._operations.append(
TransactOperation(
{
'Get': dict(
TableName=table_name,
Key=serialize(key),
**attrs,
)
}
),
)
def delete( def delete(
self, self,
*, *,
key: dict, key: dict,
table_name: str | None = None, table_name: str | None = None,
cond_expr: str | None = None, cond_expr: str | None = None,
expr_attr_names: dict = {}, expr_attr_names: dict | None = None,
expr_attr_values: dict = {}, expr_attr_values: dict | None = None,
exc_cls: Type[Exception] | None = None, exc_cls: Type[Exception] | None = None,
) -> None: ) -> None:
attrs: dict = {} attrs: dict = {}
@@ -489,7 +466,7 @@ class TransactItems:
if not table_name: if not table_name:
table_name = self._table_name table_name = self._table_name
self._operations.append( self._add_op_and_process(
TransactOperation( TransactOperation(
{ {
'Delete': dict( 'Delete': dict(
@@ -508,8 +485,8 @@ class TransactItems:
key: dict, key: dict,
cond_expr: str, cond_expr: str,
table_name: str | None = None, table_name: str | None = None,
expr_attr_names: dict = {}, expr_attr_names: dict | None = None,
expr_attr_values: dict = {}, expr_attr_values: dict | None = None,
exc_cls: Type[Exception] | None = None, exc_cls: Type[Exception] | None = None,
) -> None: ) -> None:
attrs: dict = {'ConditionExpression': cond_expr} attrs: dict = {'ConditionExpression': cond_expr}
@@ -523,7 +500,7 @@ class TransactItems:
if not table_name: if not table_name:
table_name = self._table_name table_name = self._table_name
self._operations.append( self._add_op_and_process(
TransactOperation( TransactOperation(
{ {
'ConditionCheck': dict( 'ConditionCheck': dict(
@@ -536,13 +513,21 @@ class TransactItems:
) )
) )
def write_items(self) -> bool: def _add_op_and_process(self, op: TransactOperation) -> None:
operations = self._operations.copy() self._items_buffer.append(op)
self._operations.clear() self._flush_if_needed()
def _flush_if_needed(self) -> None:
if len(self._items_buffer) >= self._flush_amount:
self._flush()
def _flush(self) -> bool:
items_to_send = self._items_buffer[: self._flush_amount]
self._items_buffer = self._items_buffer[self._flush_amount :]
try: try:
self._client.transact_write_items( self._client.transact_write_items(
TransactItems=[item.op for item in operations] # type: ignore TransactItems=[item.operation for item in items_to_send] # type: ignore
) )
except ClientError as err: except ClientError as err:
error_msg = glom(err, 'response.Error.Message', default='') error_msg = glom(err, 'response.Error.Message', default='')
@@ -553,7 +538,7 @@ class TransactItems:
if 'Message' not in reason: if 'Message' not in reason:
continue continue
item = operations[idx] item = items_to_send[idx]
if item.exc_cls: if item.exc_cls:
raise item.exc_cls(error_msg) raise item.exc_cls(error_msg)
@@ -562,7 +547,7 @@ class TransactItems:
{ {
'code': reason.get('Code'), 'code': reason.get('Code'),
'message': reason.get('Message'), 'message': reason.get('Message'),
'operation': item.op, 'operation': item.operation,
} }
) )
@@ -570,32 +555,11 @@ class TransactItems:
else: else:
return True return True
def get_items(self) -> list[dict[str, Any]]:
operations = self._operations.copy()
self._operations.clear()
try:
response = self._client.transact_get_items(
TransactItems=[item.op for item in operations] # type: ignore
)
except ClientError as err:
logger.exception(err)
raise
else:
return [
deserialize(response.get('Item', {}))
for response in response.get('Responses', [])
]
class DynamoDBPersistenceLayer: class DynamoDBPersistenceLayer:
def __init__(self, table_name: str, client: DynamoDBClient) -> None: def __init__(self, table_name: str, client: DynamoDBClient) -> None:
self._table_name = table_name self.table_name = table_name
self._client = client self.client = client
@property
def collect(self) -> 'DynamoDBCollection':
return DynamoDBCollection(self)
def query( def query(
self, self,
@@ -625,7 +589,7 @@ class DynamoDBPersistenceLayer:
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/query.html - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/query.html
""" """
attrs: dict = { attrs: dict = {
'TableName': self._table_name, 'TableName': self.table_name,
'KeyConditionExpression': key_cond_expr, 'KeyConditionExpression': key_cond_expr,
'ScanIndexForward': index_forward, 'ScanIndexForward': index_forward,
} }
@@ -646,7 +610,7 @@ class DynamoDBPersistenceLayer:
attrs['Limit'] = limit attrs['Limit'] = limit
try: try:
response = self._client.query(**attrs) response = self.client.query(**attrs)
except ClientError as err: except ClientError as err:
logger.info(attrs) logger.info(attrs)
logger.exception(err) logger.exception(err)
@@ -665,12 +629,12 @@ class DynamoDBPersistenceLayer:
there will be no Item element in the response. there will be no Item element in the response.
""" """
attrs = { attrs = {
'TableName': self._table_name, 'TableName': self.table_name,
'Key': serialize(key), 'Key': serialize(key),
} }
try: try:
response = self._client.get_item(**attrs) response = self.client.get_item(**attrs)
except ClientError as err: except ClientError as err:
logger.info(attrs) logger.info(attrs)
logger.exception(err) logger.exception(err)
@@ -680,7 +644,7 @@ class DynamoDBPersistenceLayer:
def put_item(self, item: dict, *, cond_expr: str | None = None) -> bool: def put_item(self, item: dict, *, cond_expr: str | None = None) -> bool:
attrs = { attrs = {
'TableName': self._table_name, 'TableName': self.table_name,
'Item': serialize(item), 'Item': serialize(item),
} }
@@ -688,7 +652,7 @@ class DynamoDBPersistenceLayer:
attrs['ConditionExpression'] = cond_expr attrs['ConditionExpression'] = cond_expr
try: try:
self._client.put_item(**attrs) self.client.put_item(**attrs)
except ClientError as err: except ClientError as err:
logger.info(attrs) logger.info(attrs)
logger.exception(err) logger.exception(err)
@@ -706,7 +670,7 @@ class DynamoDBPersistenceLayer:
expr_attr_values: dict | None = None, expr_attr_values: dict | None = None,
) -> bool: ) -> bool:
attrs: dict = { attrs: dict = {
'TableName': self._table_name, 'TableName': self.table_name,
'Key': serialize(key), 'Key': serialize(key),
'UpdateExpression': update_expr, 'UpdateExpression': update_expr,
} }
@@ -721,7 +685,7 @@ class DynamoDBPersistenceLayer:
attrs['ExpressionAttributeValues'] = serialize(expr_attr_values) attrs['ExpressionAttributeValues'] = serialize(expr_attr_values)
try: try:
self._client.update_item(**attrs) self.client.update_item(**attrs)
except ClientError as err: except ClientError as err:
logger.info(attrs) logger.info(attrs)
logger.exception(err) logger.exception(err)
@@ -742,7 +706,7 @@ class DynamoDBPersistenceLayer:
or if it has an expected attribute value. or if it has an expected attribute value.
""" """
attrs: dict = { attrs: dict = {
'TableName': self._table_name, 'TableName': self.table_name,
'Key': serialize(key), 'Key': serialize(key),
} }
@@ -756,7 +720,7 @@ class DynamoDBPersistenceLayer:
attrs['ExpressionAttributeValues'] = serialize(expr_attr_values) attrs['ExpressionAttributeValues'] = serialize(expr_attr_values)
try: try:
self._client.delete_item(**attrs) self.client.delete_item(**attrs)
except ClientError as err: except ClientError as err:
logger.info(attrs) logger.info(attrs)
logger.exception(err) logger.exception(err)
@@ -764,8 +728,16 @@ class DynamoDBPersistenceLayer:
else: else:
return True return True
def transact_items(self) -> TransactItems: @property
return TransactItems(table_name=self._table_name, client=self._client) def collection(self) -> 'DynamoDBCollection':
return DynamoDBCollection(self)
def transact_writer(self, flush_amount: int = 50) -> TransactWriter:
return TransactWriter(
table_name=self.table_name,
client=self.client,
flush_amount=flush_amount,
)
def batch_writer( def batch_writer(
self, self,
@@ -797,8 +769,8 @@ class DynamoDBPersistenceLayer:
DynamoDB.Table.batch_writer https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/batch_writer.html#DynamoDB.Table.batch_writer DynamoDB.Table.batch_writer https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/batch_writer.html#DynamoDB.Table.batch_writer
""" """
return BatchWriter( return BatchWriter(
table_name=table_name or self._table_name, table_name=table_name or self.table_name,
client=self._client, client=self.client,
overwrite_by_pkeys=overwrite_by_pkeys, overwrite_by_pkeys=overwrite_by_pkeys,
) )
@@ -1033,15 +1005,23 @@ class DynamoDBCollection:
if not key.pairs: if not key.pairs:
return {} return {}
items = []
sortkeys = key.pairs[1:] if flatten_top else key.pairs sortkeys = key.pairs[1:] if flatten_top else key.pairs
client = self.persistence_layer.client
table_name = self.persistence_layer.table_name
with self.persistence_layer.transact_items() as transact:
# Add a get operation for each key for the transaction # Add a get operation for each key for the transaction
for pair in key.pairs: transact_items = [
transact.get(key=pair) {
'Get': {
'TableName': getattr(pair, 'table_name', table_name),
'Key': serialize(pair),
}
}
for pair in key.pairs
]
items = transact.get_items() response = client.transact_get_items(TransactItems=transact_items) # type: ignore
items = [deserialize(r.get('Item', {})) for r in response.get('Responses', [])]
if flatten_top: if flatten_top:
head, *tail = items head, *tail = items

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "layercake" name = "layercake"
version = "0.5.0" version = "0.6.2"
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

@@ -93,8 +93,8 @@ def test_transact_write_items(
): ):
class EmailConflictError(Exception): ... class EmailConflictError(Exception): ...
with dynamodb_persistence_layer.transact_items() as transact: with pytest.raises(EmailConflictError):
# transact = TransactItems(dynamodb_persistence_layer.table_name) with dynamodb_persistence_layer.transact_writer(flush_amount=2) as transact:
transact.put(item=KeyPair('5OxmMjL-ujoR5IMGegQz', '0')) transact.put(item=KeyPair('5OxmMjL-ujoR5IMGegQz', '0'))
transact.put(item=KeyPair('cpf', '07879819908')) transact.put(item=KeyPair('cpf', '07879819908'))
transact.put( transact.put(
@@ -110,16 +110,13 @@ def test_transact_write_items(
exc_cls=EmailConflictError, exc_cls=EmailConflictError,
) )
with pytest.raises(EmailConflictError):
transact.write_items()
def test_collection_get_item( def test_collection_get_item(
dynamodb_seeds, dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer, dynamodb_persistence_layer: DynamoDBPersistenceLayer,
): ):
collect = dynamodb_persistence_layer.collect collection = dynamodb_persistence_layer.collection
data_notfound = collect.get_item( data_notfound = collection.get_item(
KeyPair( KeyPair(
pk='5OxmMjL-ujoR5IMGegQz', pk='5OxmMjL-ujoR5IMGegQz',
sk='tenant', sk='tenant',
@@ -130,7 +127,7 @@ def test_collection_get_item(
assert data_notfound == {} assert data_notfound == {}
# This data was added from seeds # This data was added from seeds
data = collect.get_item( data = collection.get_item(
KeyPair( KeyPair(
pk='5OxmMjL-ujoR5IMGegQz', pk='5OxmMjL-ujoR5IMGegQz',
sk=ComposeKey('sergio@somosbeta.com.br', prefix='emails'), sk=ComposeKey('sergio@somosbeta.com.br', prefix='emails'),
@@ -150,7 +147,7 @@ def test_collection_get_item(
class NotFoundError(Exception): ... class NotFoundError(Exception): ...
with pytest.raises(NotFoundError): with pytest.raises(NotFoundError):
collect.get_item( collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', 'notfound'), KeyPair('5OxmMjL-ujoR5IMGegQz', 'notfound'),
exc_cls=NotFoundError, exc_cls=NotFoundError,
) )
@@ -160,10 +157,10 @@ def test_collection_get_item_path_spec(
dynamodb_seeds, dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer, dynamodb_persistence_layer: DynamoDBPersistenceLayer,
): ):
collect = dynamodb_persistence_layer.collect collection = dynamodb_persistence_layer.collection
# This data was added from seeds # This data was added from seeds
data = collect.get_item( data = collection.get_item(
KeyPair( KeyPair(
pk='5OxmMjL-ujoR5IMGegQz', pk='5OxmMjL-ujoR5IMGegQz',
sk=SortKey( sk=SortKey(

2
layercake/uv.lock generated
View File

@@ -589,7 +589,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.4.0" version = "0.6.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },

View File

@@ -9,7 +9,6 @@ from layercake.dynamodb import (
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
SortKey, SortKey,
TransactItems,
) )
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
@@ -25,7 +24,7 @@ order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] new_image = event.detail['new_image']
now_ = now() now_ = now()
ids = user_layer.collect.get_items( ids = user_layer.collection.get_items(
KeyPair( KeyPair(
pk='cnpj', pk='cnpj',
sk=SortKey(new_image['cnpj'], path_spec='user_id'), sk=SortKey(new_image['cnpj'], path_spec='user_id'),
@@ -44,11 +43,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
if len(ids) < 2: if len(ids) < 2:
raise ValueError('IDs not found.') raise ValueError('IDs not found.')
transact = TransactItems(order_layer.table_name) with order_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(new_image['id'], '0'), key=KeyPair(new_image['id'], '0'),
update_expr='SET metadata__tenant_id = :tenant_id, \ update_expr='SET metadata__tenant_id = :tenant_id, \
metadata__related_ids = :related_ids, update_date = :update_date', metadata__related_ids = :related_ids, \
update_date = :update_date',
expr_attr_values={ expr_attr_values={
':tenant_id': ids['org_id'], ':tenant_id': ids['org_id'],
':related_ids': set(ids.values()), ':related_ids': set(ids.values()),
@@ -68,11 +68,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
transact.put( transact.put(
item={ item={
'id': new_image['id'], 'id': new_image['id'],
'sk': 'related_ids#%s' % k.removesuffix('_id'), # e.g. related_ids#user 'sk': 'related_ids#%s'
% k.removesuffix('_id'), # e.g. related_ids#user
'create_date': now_, 'create_date': now_,
k: v, k: v,
} }
) )
order_layer.transact_write_items(transact)
return True return True

View File

@@ -8,7 +8,6 @@ from layercake.dateutils import now
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
TransactItems,
) )
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
@@ -23,7 +22,8 @@ order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] new_image = event.detail['new_image']
now_ = now() now_ = now()
transact = TransactItems(order_layer.table_name)
with order_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(new_image['id'], '0'), key=KeyPair(new_image['id'], '0'),
update_expr='SET #status = :status, update_date = :update_date', update_expr='SET #status = :status, update_date = :update_date',
@@ -42,5 +42,5 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'create_date': now_, 'create_date': now_,
} }
) )
order_layer.transact_write_items(transact)
return True return True

View File

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

View File

@@ -21,7 +21,7 @@ def test_assign_tenant_cnpj(
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore
result = dynamodb_persistence_layer.collect.query( result = dynamodb_persistence_layer.collection.query(
PartitionKey('9omWNKymwU5U4aeun6mWzZ') PartitionKey('9omWNKymwU5U4aeun6mWzZ')
) )

View File

@@ -495,7 +495,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.4.0" version = "0.6.2"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },