update layercake version
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
2
enrollment-management/uv.lock
generated
2
enrollment-management/uv.lock
generated
@@ -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
36
http-api/app/config.py
Normal 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'
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
55
http-api/app/routes/enrollments/cancel.py
Normal file
55
http-api/app/routes/enrollments/cancel.py
Normal 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
|
||||||
64
http-api/app/routes/enrollments/enroll.py
Normal file
64
http-api/app/routes/enrollments/enroll.py
Normal 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,
|
||||||
|
# )
|
||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
2
http-api/uv.lock
generated
@@ -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" },
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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
2
layercake/uv.lock
generated
@@ -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" },
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
2
order-management/uv.lock
generated
2
order-management/uv.lock
generated
@@ -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" },
|
||||||
|
|||||||
Reference in New Issue
Block a user