add docs
This commit is contained in:
@@ -13,3 +13,4 @@ if 'PYTEST_VERSION' in os.environ:
|
|||||||
DYNAMODB_ENDPOINT_URL = 'http://127.0.0.1:8000'
|
DYNAMODB_ENDPOINT_URL = 'http://127.0.0.1:8000'
|
||||||
|
|
||||||
dynamodb_client = boto3.client('dynamodb', endpoint_url=DYNAMODB_ENDPOINT_URL)
|
dynamodb_client = boto3.client('dynamodb', endpoint_url=DYNAMODB_ENDPOINT_URL)
|
||||||
|
idp_client = boto3.client('cognito-idp')
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ class Elastic:
|
|||||||
index: str,
|
index: str,
|
||||||
doc: dict,
|
doc: dict,
|
||||||
):
|
):
|
||||||
return self.client.index(
|
return self.client.update(
|
||||||
index=index,
|
index=index,
|
||||||
id=id,
|
id=id,
|
||||||
document=_serialize_to_basic_types(doc),
|
doc=_serialize_to_basic_types(doc),
|
||||||
|
doc_as_upsert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_index(self, index: str) -> bool:
|
def delete_index(self, index: str) -> bool:
|
||||||
@@ -90,12 +91,12 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Populate DynamoDB tables with data from JSONL files
|
# Populate DynamoDB tables with data from JSONL files
|
||||||
for file in tqdm(jsonl_files, desc='Processing files'):
|
for file in tqdm(jsonl_files, desc='Processing files'):
|
||||||
with jsonlines.open(f'seeds/{file}') as lines:
|
with open(f'seeds/{file}') as fp:
|
||||||
table_name = file.removesuffix('.jsonl')
|
table_name = file.removesuffix('.jsonl')
|
||||||
reader = jsonlines.Reader(fp)
|
reader = jsonlines.Reader(fp).iter(skip_invalid=True)
|
||||||
|
|
||||||
for line in tqdm(lines, desc=f'Processing lines in {file}'):
|
for line in tqdm(reader, desc=f'Processing lines in {file}'):
|
||||||
put_item(line, table_name, dynamodb_client)
|
put_item(line, table_name, dynamodb_client) # type: ignore
|
||||||
|
|
||||||
# Scan DynamoDB tables and index the data into Elasticsearch
|
# Scan DynamoDB tables and index the data into Elasticsearch
|
||||||
for file in tqdm(jsonl_files, desc='Scanning tables'):
|
for file in tqdm(jsonl_files, desc='Scanning tables'):
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class UnauthorizedError(Exception):
|
class UnauthorizedError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -10,3 +15,24 @@ def get_user(access_token: str, /, idp_client) -> dict[str, str]:
|
|||||||
raise UnauthorizedError()
|
raise UnauthorizedError()
|
||||||
else:
|
else:
|
||||||
return {attr['Name']: attr['Value'] for attr in user['UserAttributes']}
|
return {attr['Name']: attr['Value'] for attr in user['UserAttributes']}
|
||||||
|
|
||||||
|
|
||||||
|
def admin_get_user(
|
||||||
|
sub: str,
|
||||||
|
user_pool_id: str,
|
||||||
|
*,
|
||||||
|
idp_client,
|
||||||
|
) -> dict[str, str] | None:
|
||||||
|
"""Gets the specified user by user name in a user pool as an administrator.
|
||||||
|
Works on any user.
|
||||||
|
|
||||||
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp/client/admin_get_user.html
|
||||||
|
- https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminGetUser.html
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = idp_client.admin_get_user(Username=sub, UserPoolId=user_pool_id)
|
||||||
|
except idp_client.exceptions as err:
|
||||||
|
logger.exception(err)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return user
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ def create_course(
|
|||||||
/,
|
/,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
):
|
):
|
||||||
current_time = now()
|
now_ = now()
|
||||||
transact = TransactItems(persistence_layer.table_name)
|
transact = TransactItems(persistence_layer.table_name)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'sk': '0',
|
'sk': '0',
|
||||||
'tenant__org_id': {org.id},
|
'tenant__org_id': {org.id},
|
||||||
'create_date': current_time,
|
'create_date': now_,
|
||||||
**course.model_dump(),
|
**course.model_dump(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -26,7 +26,7 @@ def create_course(
|
|||||||
'sk': 'tenant',
|
'sk': 'tenant',
|
||||||
'org_id': org.id,
|
'org_id': org.id,
|
||||||
'name': org.name,
|
'name': org.name,
|
||||||
'create_date': current_time,
|
'create_date': now_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return persistence_layer.transact_write_items(transact)
|
return persistence_layer.transact_write_items(transact)
|
||||||
@@ -38,7 +38,7 @@ def update_course(
|
|||||||
/,
|
/,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
):
|
):
|
||||||
current_time = now()
|
now_ = now()
|
||||||
transact = TransactItems(persistence_layer.table_name)
|
transact = TransactItems(persistence_layer.table_name)
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, '0'),
|
key=KeyPair(id, '0'),
|
||||||
@@ -50,7 +50,7 @@ def update_course(
|
|||||||
':name': course.name,
|
':name': course.name,
|
||||||
':cert': course.cert.model_dump() if course.cert else None,
|
':cert': course.cert.model_dump() if course.cert else None,
|
||||||
':access_period': course.access_period,
|
':access_period': course.access_period,
|
||||||
':update_date': current_time,
|
':update_date': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_exists(sk)',
|
cond_expr='attribute_exists(sk)',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Literal
|
from typing import TypedDict
|
||||||
|
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from elasticsearch import Elasticsearch
|
from elasticsearch import Elasticsearch
|
||||||
from pydantic import BaseModel
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBCollection,
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
SortKey,
|
||||||
|
TransactKey,
|
||||||
|
)
|
||||||
|
from pydantic import UUID4, BaseModel
|
||||||
|
|
||||||
import elastic
|
import elastic
|
||||||
from settings import ELASTIC_CONN, ENROLLMENT_TABLE
|
from boto3clients import dynamodb_client
|
||||||
|
from middlewares.audit_log_middleware import AuditLogMiddleware
|
||||||
|
from middlewares.authorizer_middleware import User
|
||||||
|
from settings import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
|
enrollment_collect = DynamoDBCollection(enrollment_layer)
|
||||||
|
user_collect = DynamoDBCollection(user_layer)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/', compress=True, tags=['Enrollment'])
|
@router.get('/', compress=True, tags=['Enrollment'])
|
||||||
@@ -28,16 +41,44 @@ def get_enrollments():
|
|||||||
|
|
||||||
@router.get('/<id>', compress=True, tags=['Enrollment'])
|
@router.get('/<id>', compress=True, tags=['Enrollment'])
|
||||||
def get_enrollment(id: str):
|
def get_enrollment(id: str):
|
||||||
return {}
|
return enrollment_collect.get_items(
|
||||||
|
TransactKey(id)
|
||||||
|
+ SortKey('0')
|
||||||
|
+ SortKey('started_date')
|
||||||
|
+ SortKey('finished_date')
|
||||||
|
+ SortKey('failed_date')
|
||||||
|
+ SortKey('canceled_date')
|
||||||
|
+ SortKey('archived_date')
|
||||||
|
+ SortKey('cancel_policy')
|
||||||
|
+ SortKey('parent_vacancy')
|
||||||
|
+ SortKey('lock', path_spec='hash')
|
||||||
|
+ SortKey('author')
|
||||||
|
+ SortKey('tenant')
|
||||||
|
+ SortKey('cert')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CancelPayload(BaseModel):
|
class Course(TypedDict):
|
||||||
status: Literal['CANCELED'] = 'CANCELED'
|
id: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
@router.patch('/<id>', compress=True, tags=['Enrollment'])
|
class Cancel(BaseModel):
|
||||||
def cancel(id: str, payload: CancelPayload):
|
id: UUID4 | str
|
||||||
return {}
|
course: Course
|
||||||
|
|
||||||
|
|
||||||
|
@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']
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
@router.post('/', compress=True, tags=['Enrollment'])
|
@router.post('/', compress=True, tags=['Enrollment'])
|
||||||
|
|||||||
@@ -22,11 +22,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.get_items(
|
acls = user_collect.query(
|
||||||
KeyPair(user.id, PrefixKey('acls')),
|
KeyPair(user.id, PrefixKey('acls')),
|
||||||
limit=LIMIT,
|
limit=LIMIT,
|
||||||
)
|
)
|
||||||
tenants = user_collect.get_items(
|
tenants = user_collect.query(
|
||||||
KeyPair(user.id, PrefixKey('orgs')),
|
KeyPair(user.id, PrefixKey('orgs')),
|
||||||
limit=LIMIT,
|
limit=LIMIT,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ from layercake.dynamodb import (
|
|||||||
)
|
)
|
||||||
from pydantic import UUID4, BaseModel, StringConstraints
|
from pydantic import UUID4, BaseModel, StringConstraints
|
||||||
|
|
||||||
|
import cognito
|
||||||
import elastic
|
import elastic
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client, idp_client
|
||||||
from middlewares import AuditLogMiddleware
|
from middlewares import AuditLogMiddleware
|
||||||
from models import User
|
from models import User
|
||||||
from settings import ELASTIC_CONN, USER_TABLE
|
from settings import ELASTIC_CONN, USER_POOOL_ID, USER_TABLE
|
||||||
|
|
||||||
|
|
||||||
class BadRequestError(MissingError, PowertoolsBadRequestError): ...
|
class BadRequestError(MissingError, PowertoolsBadRequestError): ...
|
||||||
@@ -37,12 +38,7 @@ user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
|
|||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get('/', compress=True, tags=['User'], summary='Get users')
|
||||||
'/',
|
|
||||||
compress=True,
|
|
||||||
tags=['User'],
|
|
||||||
summary='Get users',
|
|
||||||
)
|
|
||||||
def get_users():
|
def get_users():
|
||||||
event = router.current_event
|
event = router.current_event
|
||||||
query = event.get_query_string_value('query', '{}')
|
query = event.get_query_string_value('query', '{}')
|
||||||
@@ -67,29 +63,28 @@ def post_user(payload: User):
|
|||||||
return Response(status_code=HTTPStatus.CREATED)
|
return Response(status_code=HTTPStatus.CREATED)
|
||||||
|
|
||||||
|
|
||||||
class NewPasswordPayload(BaseModel):
|
class NewPassword(BaseModel):
|
||||||
cognito_sub: UUID4
|
cognito_sub: UUID4
|
||||||
new_password: Annotated[str, StringConstraints(min_length=6)]
|
new_password: Annotated[str, StringConstraints(min_length=6)]
|
||||||
|
|
||||||
|
|
||||||
@router.patch('/<id>', compress=True, tags=['User'])
|
@router.patch('/<id>', compress=True, tags=['User'])
|
||||||
def patch_reset(id: str, payload: NewPasswordPayload):
|
def patch_newpassword(id: str, payload: NewPassword):
|
||||||
return Response(status_code=HTTPStatus.OK)
|
return Response(status_code=HTTPStatus.OK)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get('/<id>', compress=True, tags=['User'], summary='Get user')
|
||||||
'/<id>',
|
|
||||||
compress=True,
|
|
||||||
tags=['User'],
|
|
||||||
summary='Get user',
|
|
||||||
)
|
|
||||||
def get_user(id: str):
|
def get_user(id: str):
|
||||||
return user_collect.get_item(KeyPair(id, '0'))
|
return user_collect.get_item(KeyPair(id, '0'))
|
||||||
|
|
||||||
|
|
||||||
@router.get('/<id>/idp', compress=True, include_in_schema=False)
|
@router.get('/<id>/idp', compress=True, include_in_schema=False)
|
||||||
def get_idp(id: str):
|
def get_idp(id: str):
|
||||||
return []
|
return cognito.admin_get_user(
|
||||||
|
sub=id,
|
||||||
|
user_pool_id=USER_POOOL_ID,
|
||||||
|
idp_client=idp_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -99,7 +94,7 @@ def get_idp(id: str):
|
|||||||
summary='Get user emails',
|
summary='Get user emails',
|
||||||
)
|
)
|
||||||
def get_emails(id: str):
|
def get_emails(id: str):
|
||||||
return user_collect.get_items(
|
return user_collect.query(
|
||||||
KeyPair(id, PrefixKey('emails')),
|
KeyPair(id, PrefixKey('emails')),
|
||||||
start_key=router.current_event.get_query_string_value('start_key', None),
|
start_key=router.current_event.get_query_string_value('start_key', None),
|
||||||
)
|
)
|
||||||
@@ -112,7 +107,7 @@ def get_emails(id: str):
|
|||||||
summary='Get user logs',
|
summary='Get user logs',
|
||||||
)
|
)
|
||||||
def get_logs(id: str):
|
def get_logs(id: str):
|
||||||
return user_collect.get_items(
|
return user_collect.query(
|
||||||
# Post-migration: uncomment to enable PartitionKey with a composite key (id with `logs` prefix).
|
# Post-migration: uncomment to enable PartitionKey with a composite key (id with `logs` prefix).
|
||||||
# PartitionKey(ComposeKey(id, prefix='logs')),
|
# PartitionKey(ComposeKey(id, prefix='logs')),
|
||||||
PartitionKey(ComposeKey(id, prefix='log', delimiter=':')),
|
PartitionKey(ComposeKey(id, prefix='log', delimiter=':')),
|
||||||
@@ -127,7 +122,7 @@ def get_logs(id: str):
|
|||||||
summary='Get user orgs',
|
summary='Get user orgs',
|
||||||
)
|
)
|
||||||
def get_orgs(id: str):
|
def get_orgs(id: str):
|
||||||
return user_collect.get_items(
|
return user_collect.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),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#MqiRQWy9cSBEci93aKBvTd"}, "tag": {"S": "Test"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#MqiRQWy9cSBEci93aKBvTd"}, "tag": {"S": "Test"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
|
||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#NQ3YmmTesfoSyHCkPKGKEF"}, "tag": {"S": "F\u00e1brica"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#NQ3YmmTesfoSyHCkPKGKEF"}, "tag": {"S": "F\u00e1brica"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
|
||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#bg45io8igarjsA4BzPQyrz"}, "tag": {"S": "Escrit\u00f3rio"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#bg45io8igarjsA4BzPQyrz"}, "tag": {"S": "Escrit\u00f3rio"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
|
||||||
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "payment_policy"}, "due_days": {"N": "90"}}
|
||||||
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "billing_policy"}, "billing_day": {"N": "2"}, "payment_method": {"S": "PIX"}}
|
||||||
{"sk": {"S": "admin#18606"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "mateushickmann@petrobras.com.br"}, "name": {"S": "MATEUS LATTIK HICKMANN"}}
|
{"sk": {"S": "admin#18606"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "mateushickmann@petrobras.com.br"}, "name": {"S": "MATEUS LATTIK HICKMANN"}}
|
||||||
{"sk": {"S": "admin#21425"}, "create_date": {"S": "2023-09-22T18:34:06.480230-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "sergio@inbep.com.br"}, "name": {"S": "S\u00e9rgio Siqueira"}}
|
{"sk": {"S": "admin#21425"}, "create_date": {"S": "2023-09-22T18:34:06.480230-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "sergio@inbep.com.br"}, "name": {"S": "S\u00e9rgio Siqueira"}}
|
||||||
{"sk": {"S": "admin#2338"}, "create_date": {"S": "2023-09-22T18:28:10.121068-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "empresa@inbep.com.br"}, "name": {"S": "Empresa Teste"}}
|
{"sk": {"S": "admin#2338"}, "create_date": {"S": "2023-09-22T18:28:10.121068-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "empresa@inbep.com.br"}, "name": {"S": "Empresa Teste"}}
|
||||||
@@ -68,3 +70,6 @@
|
|||||||
{"id": {"S": "webhook_requests#*#0e7f4d2e62ec525fc94465a6dd7299d2"}, "sk": {"S": "2025-03-03T15:47:42.039256-03:00"}, "request": {"M": {"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "headers": {"M": {"Accept": {"S": "*/*"}, "Accept-Encoding": {"S": "gzip, deflate"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "108"}, "Content-Type": {"S": "application/json"}, "User-Agent": {"S": "eduseg/python-requests/2.32.3"}}}, "payload": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}}}, "response": {"M": {"body": {"S": "{\"message\":\"Workflow was started\"}"}, "elapsed_time": {"S": "0.14"}, "headers": {"M": {"alt-svc": {"S": "h3=\":443\"; ma=86400"}, "cf-cache-status": {"S": "DYNAMIC"}, "CF-RAY": {"S": "8fc528b57b62a3f0-GRU"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "34"}, "Content-Type": {"S": "application/json; charset=utf-8"}, "Date": {"S": "Fri, 03 Jan 2025 18:47:44 GMT"}, "etag": {"S": "W/\"22-6OS7cK0FzqnV2NeDHdOSGS1bVUs\""}, "NEL": {"S": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"}, "Report-To": {"S": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=FMkgKCTOnFvNgtE30yiYnM4XtK8q99O62s7Ep57KDNc3YGiy3W1j%2BzfS0vqJNylSAn5viU3MMGJvTIwPsYQ6jQ298t1p0hYd1KPwURhxchME4hc%2BsO0NNAv%2FFEpXv1ZYWw%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}"}, "Server": {"S": "cloudflare"}, "server-timing": {"S": "cfL4;desc=\"?proto=TCP&rtt=1016&min_rtt=998&rtt_var=411&sent=4&recv=7&lost=0&retrans=0&sent_bytes=2836&recv_bytes=999&delivery_rate=2522648&cwnd=251&unsent_bytes=0&cid=1b26053952909423&ts=93&x=0\""}, "Strict-Transport-Security": {"S": "max-age=0; includeSubDomains"}, "vary": {"S": "Accept-Encoding"}}}, "status_code": {"S": "200"}}}, "update_date": {"S": "2025-01-03T15:47:44.537597-03:00"}, "url": {"S": "https://n8n.sergio.run/webhook/56bb43b8-533c-4e8b-bdaa-3f7c2b0e548f"}}
|
{"id": {"S": "webhook_requests#*#0e7f4d2e62ec525fc94465a6dd7299d2"}, "sk": {"S": "2025-03-03T15:47:42.039256-03:00"}, "request": {"M": {"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "headers": {"M": {"Accept": {"S": "*/*"}, "Accept-Encoding": {"S": "gzip, deflate"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "108"}, "Content-Type": {"S": "application/json"}, "User-Agent": {"S": "eduseg/python-requests/2.32.3"}}}, "payload": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}}}, "response": {"M": {"body": {"S": "{\"message\":\"Workflow was started\"}"}, "elapsed_time": {"S": "0.14"}, "headers": {"M": {"alt-svc": {"S": "h3=\":443\"; ma=86400"}, "cf-cache-status": {"S": "DYNAMIC"}, "CF-RAY": {"S": "8fc528b57b62a3f0-GRU"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "34"}, "Content-Type": {"S": "application/json; charset=utf-8"}, "Date": {"S": "Fri, 03 Jan 2025 18:47:44 GMT"}, "etag": {"S": "W/\"22-6OS7cK0FzqnV2NeDHdOSGS1bVUs\""}, "NEL": {"S": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"}, "Report-To": {"S": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=FMkgKCTOnFvNgtE30yiYnM4XtK8q99O62s7Ep57KDNc3YGiy3W1j%2BzfS0vqJNylSAn5viU3MMGJvTIwPsYQ6jQ298t1p0hYd1KPwURhxchME4hc%2BsO0NNAv%2FFEpXv1ZYWw%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}"}, "Server": {"S": "cloudflare"}, "server-timing": {"S": "cfL4;desc=\"?proto=TCP&rtt=1016&min_rtt=998&rtt_var=411&sent=4&recv=7&lost=0&retrans=0&sent_bytes=2836&recv_bytes=999&delivery_rate=2522648&cwnd=251&unsent_bytes=0&cid=1b26053952909423&ts=93&x=0\""}, "Strict-Transport-Security": {"S": "max-age=0; includeSubDomains"}, "vary": {"S": "Accept-Encoding"}}}, "status_code": {"S": "200"}}}, "update_date": {"S": "2025-01-03T15:47:44.537597-03:00"}, "url": {"S": "https://n8n.sergio.run/webhook/56bb43b8-533c-4e8b-bdaa-3f7c2b0e548f"}}
|
||||||
{"id": {"S": "webhooks#*"}, "sk": {"S": "1e3759c9c4cfa7aaf86ac281bdb8fd6f"}, "author": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}, "create_date": {"S": "2025-01-06T15:00:39.363207-03:00"}, "event_type": {"S": "insert"}, "resource": {"S": "users"}, "url": {"S": "https://hook.us2.make.com/hgkc5oj5dbpfld5qvu1xmsu22ond93l9"}}
|
{"id": {"S": "webhooks#*"}, "sk": {"S": "1e3759c9c4cfa7aaf86ac281bdb8fd6f"}, "author": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}, "create_date": {"S": "2025-01-06T15:00:39.363207-03:00"}, "event_type": {"S": "insert"}, "resource": {"S": "users"}, "url": {"S": "https://hook.us2.make.com/hgkc5oj5dbpfld5qvu1xmsu22ond93l9"}}
|
||||||
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"},"sk": {"S": "2024-02-26T11:48:17.605911-03:00"},"action": {"S": "OPEN_EMAIL"},"data": {"M": {"ip_address": {"S": "66.249.83.73"},"message_id": {"S": "0103018de5de75cf-670bf01f-7ccf-4ce0-88b4-7c85cd0d06d0-000000"},"recipients": {"L": [{"S": "sergio@somosbeta.com.br"}]},"subject": {"S": "Re: (sem assunto)"},"timestamp": {"S": "2024-02-26T14:48:16.976Z"},"user_agent": {"S": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"}}},"ttl": {"N": "1772030897"}}
|
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"},"sk": {"S": "2024-02-26T11:48:17.605911-03:00"},"action": {"S": "OPEN_EMAIL"},"data": {"M": {"ip_address": {"S": "66.249.83.73"},"message_id": {"S": "0103018de5de75cf-670bf01f-7ccf-4ce0-88b4-7c85cd0d06d0-000000"},"recipients": {"L": [{"S": "sergio@somosbeta.com.br"}]},"subject": {"S": "Re: (sem assunto)"},"timestamp": {"S": "2024-02-26T14:48:16.976Z"},"user_agent": {"S": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"}}},"ttl": {"N": "1772030897"}}
|
||||||
|
{"id": {"S": "7zf52CWrTS3csRBFWU5rkq"},"sk": {"S": "0"},"cognito:sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"},"cpf": {"S": "04330965275"},"createDate": {"S": "2025-04-08T10:24:46.493980-03:00"},"email": {"S": "barbara.gomes@sinobras.com.br"},"email_verified": {"BOOL": true},"konviva:id": {"N": "199205"},"lastLogin": {"S": "2025-04-10T12:01:48.380215-03:00"},"name": {"S": "Barbara Kamyla Vasconcelos Gomes"},"tenant__org_id": {"SS": ["EkvQwpmmL6vzWtJunM5dCJ"]},"update_date": {"S": "2025-04-10T08:50:22.530758-03:00"}}
|
||||||
|
{"id": {"S": "7zf52CWrTS3csRBFWU5rkq"},"sk": {"S": "acls#EkvQwpmmL6vzWtJunM5dCJ"},"create_date": {"S": "2025-04-10T09:36:41.133157-03:00"},"roles": {"L": [{"S": "ADMIN"}]}}
|
||||||
|
{"id": {"S": "7zf52CWrTS3csRBFWU5rkq"},"sk": {"S": "orgs#EkvQwpmmL6vzWtJunM5dCJ"},"cnpj": {"S": "07933914000154"},"create_date": {"S": "2025-04-08T10:24:46.493980-03:00"},"name": {"S": "SIDERURGICA NORTE BRASIL S.A"}}
|
||||||
|
|||||||
@@ -32,3 +32,5 @@ match os.getenv('AWS_SAM_LOCAL'), os.getenv('PYTEST_VERSION'):
|
|||||||
'cloud_id': ELASTIC_CLOUD_ID,
|
'cloud_id': ELASTIC_CLOUD_ID,
|
||||||
'basic_auth': ('elastic', ELASTIC_AUTH_PASS),
|
'basic_auth': ('elastic', ELASTIC_AUTH_PASS),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
USER_POOOL_ID = 'sa-east-1_s6YmVSfXj'
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Globals:
|
|||||||
Architectures:
|
Architectures:
|
||||||
- x86_64
|
- x86_64
|
||||||
Layers:
|
Layers:
|
||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:35
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:42
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
@@ -82,6 +82,15 @@ Resources:
|
|||||||
TableName: !Ref CourseTable
|
TableName: !Ref CourseTable
|
||||||
- DynamoDBCrudPolicy:
|
- DynamoDBCrudPolicy:
|
||||||
TableName: !Ref OrderTable
|
TableName: !Ref OrderTable
|
||||||
|
- DynamoDBCrudPolicy:
|
||||||
|
TableName: !Ref EnrollmentTable
|
||||||
|
- Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- cognito-idp:AdminGetUser
|
||||||
|
- cognito-idp:AdminSetUserPassword
|
||||||
|
Resource: !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*
|
||||||
Events:
|
Events:
|
||||||
Preflight:
|
Preflight:
|
||||||
Type: HttpApi
|
Type: HttpApi
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def test_post_course(
|
|||||||
assert r['statusCode'] == HTTPStatus.CREATED
|
assert r['statusCode'] == HTTPStatus.CREATED
|
||||||
|
|
||||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||||
logs = collect.get_items(
|
logs = collect.query(
|
||||||
PartitionKey(
|
PartitionKey(
|
||||||
ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='log', delimiter=':'),
|
ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='log', delimiter=':'),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,5 +8,8 @@
|
|||||||
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}}
|
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}}
|
||||||
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}}
|
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}}
|
||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}}
|
||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "payment_policies"}, "due_days": {"N": "90"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "payment_policy"}, "due_days": {"N": "90"}}
|
||||||
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "billing_policy"}, "billing_day": {"N": "1"}, "payment_method": {"S": "PIX"}}
|
||||||
{"id": {"S": "90d7f0d2-d9a4-4467-a31c-f9a7955964cf"}, "sk": {"S": "0"}, "access_period": {"N": "720"}, "create_date": {"S": "2024-12-30T00:00:33.088916-03:00"},"konviva__class_id": {"N": "266"},"name": {"S": "Reciclagem em NR-18 Básico"},"tenant__org_id": {"SS": ["cJtK9SsnJhKPyxESe7g3DG"]}}
|
{"id": {"S": "90d7f0d2-d9a4-4467-a31c-f9a7955964cf"}, "sk": {"S": "0"}, "access_period": {"N": "720"}, "create_date": {"S": "2024-12-30T00:00:33.088916-03:00"},"konviva__class_id": {"N": "266"},"name": {"S": "Reciclagem em NR-18 Básico"},"tenant__org_id": {"SS": ["cJtK9SsnJhKPyxESe7g3DG"]}}
|
||||||
|
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "0"}, "status": {"S": "PENDING"}}
|
||||||
|
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "cancel_policy"}}
|
||||||
|
|||||||
24
http-api/uv.lock
generated
24
http-api/uv.lock
generated
@@ -521,7 +521,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.1.16"
|
version = "0.2.5"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
@@ -538,6 +538,7 @@ dependencies = [
|
|||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
|
{ name = "uuid-utils" },
|
||||||
{ name = "weasyprint" },
|
{ name = "weasyprint" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -557,6 +558,7 @@ requires-dist = [
|
|||||||
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
||||||
{ name = "pytz", specifier = ">=2025.1" },
|
{ name = "pytz", specifier = ">=2025.1" },
|
||||||
{ name = "requests", specifier = ">=2.32.3" },
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
|
{ name = "uuid-utils", specifier = ">=0.10.0" },
|
||||||
{ name = "weasyprint", specifier = ">=65.0" },
|
{ name = "weasyprint", specifier = ">=65.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -938,6 +940,26 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
|
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid-utils"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/66/0a/cbdb2eb4845dafeb632d02a18f47b02f87f2ce4f25266f5e3c017976ce89/uuid_utils-0.10.0.tar.gz", hash = "sha256:5db0e1890e8f008657ffe6ded4d9459af724ab114cfe82af1557c87545301539", size = 18828 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/54/9d22fa16b19e5d1676eba510f08a9c458d96e2a62ff2c8ebad64251afb18/uuid_utils-0.10.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d5a4508feefec62456cd6a41bcdde458d56827d908f226803b886d22a3d5e63", size = 573006 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/8e/f895c6e52aa603e521fbc13b8626ba5dd99b6e2f5a55aa96ba5b232f4c53/uuid_utils-0.10.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dbefc2b9113f9dfe56bdae58301a2b3c53792221410d422826f3d1e3e6555fe7", size = 292543 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/58/cc4834f377a5e97d6e184408ad96d13042308de56643b6e24afe1f6f34df/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffc49c33edf87d1ec8112a9b43e4cf55326877716f929c165a2cc307d31c73d5", size = 323340 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/e3/6aeddf148f6a7dd7759621b000e8c85382ec83f52ae79b60842d1dc3ab6b/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0636b6208f69d5a4e629707ad2a89a04dfa8d1023e1999181f6830646ca048a1", size = 329653 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/00/dd6c2164ace70b7b1671d9129267df331481d7d1e5f9c5e6a564f07953f6/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc06452856b724df9dedfc161c3582199547da54aeb81915ec2ed54f92d19b0", size = 365471 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/e7/0ab8080fcae5462a7b5e555c1cef3d63457baffb97a59b9bc7b005a3ecb1/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b2589111c61decdd74a762e8f850c9e4386fb78d2cf7cb4dfc537054cda1b", size = 325844 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/39/52d94e9ef75b03f44b39ffc6ac3167e93e74ef4d010a93d25589d9f48540/uuid_utils-0.10.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a558db48b7096de6b4d2d2210d82bba8586a6d55f99106b03bb7d01dc5c5bcd6", size = 344389 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/29/4824566f62666238290d99c62a58e4ab2a8b9cf2eccf94cebd9b3359131e/uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:807465067f3c892514230326ac71a79b28a8dfe2c88ecd2d5675fc844f3c76b5", size = 510078 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/8f/bbcc7130d652462c685f0d3bd26bb214b754215b476340885a4cb50fb89a/uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:57423d4a2b9d7b916de6dbd75ba85465a28f9578a89a97f7d3e098d9aa4e5d4a", size = 515937 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/f8/34e0c00f5f188604d336713e6a020fcf53b10998e8ab24735a39ab076740/uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:76d8d660f18ff6b767e319b1b5f927350cd92eafa4831d7ef5b57fdd1d91f974", size = 494111 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/52/b7f0066cc90a7a9c28d54061ed195cd617fde822e5d6ac3ccc88509c3c44/uuid_utils-0.10.0-cp39-abi3-win32.whl", hash = "sha256:6c11a71489338837db0b902b75e1ba7618d5d29f05fde4f68b3f909177dbc226", size = 173520 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/15/f04f58094674d333974243fb45d2c740cf4b79186fb707168e57943c84a3/uuid_utils-0.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:11c55ae64f6c0a7a0c741deae8ca2a4eaa11e9c09dbb7bec2099635696034cf7", size = 182965 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wcwidth"
|
name = "wcwidth"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
::: layercake.dynamodb
|
|
||||||
@@ -12,7 +12,6 @@ from uuid import UUID
|
|||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
|
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from glom import glom
|
|
||||||
|
|
||||||
from .dateutils import now, timestamp
|
from .dateutils import now, timestamp
|
||||||
from .funcs import omit
|
from .funcs import omit
|
||||||
@@ -129,12 +128,40 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SortKey(str):
|
class SortKey(str):
|
||||||
|
"""
|
||||||
|
SortKey encapsulates the sort key value and optionally stores additional attributes
|
||||||
|
for nested data extraction.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
sk: str
|
||||||
|
The sort key value.
|
||||||
|
table_name: str, optional
|
||||||
|
Optional name of the table associated with the sort key.
|
||||||
|
path_spec: str, optional
|
||||||
|
Optional specification for nested data extraction.
|
||||||
|
"""
|
||||||
|
|
||||||
sk: str
|
sk: str
|
||||||
table_name: str | None = None
|
table_name: str | None = None
|
||||||
path_spec: str | None = None
|
path_spec: str | None = None
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class SortKey(str):
|
class SortKey(str):
|
||||||
|
"""
|
||||||
|
SortKey encapsulates the sort key value and optionally stores additional attributes
|
||||||
|
for nested data extraction.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
sk: str
|
||||||
|
The sort key value.
|
||||||
|
table_name: str, optional
|
||||||
|
Optional name of the table associated with the sort key.
|
||||||
|
path_spec: str, optional
|
||||||
|
Optional specification for nested data extraction.
|
||||||
|
"""
|
||||||
|
|
||||||
def __new__(
|
def __new__(
|
||||||
cls,
|
cls,
|
||||||
sk: str,
|
sk: str,
|
||||||
@@ -574,6 +601,27 @@ class PaginatedResult(TypedDict):
|
|||||||
|
|
||||||
|
|
||||||
class DynamoDBCollection:
|
class DynamoDBCollection:
|
||||||
|
"""
|
||||||
|
DynamoDBCollection provides a high-level abstraction for performing common CRUD operations
|
||||||
|
and queries on a DynamoDB table. It leverages an underlying persistence layer to handle
|
||||||
|
serialization and deserialization of data, key composition, transaction operations, and TTL management.
|
||||||
|
|
||||||
|
This collection class simplifies interaction with DynamoDB items, allowing users to:
|
||||||
|
- Retrieve a single item or multiple items via transactions.
|
||||||
|
- Insert (put) items with optional TTL (time-to-live) settings.
|
||||||
|
- Delete items based on keys and conditions.
|
||||||
|
- Query items using partition keys or composite key pairs with optional filtering and pagination.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
persistence_layer: DynamoDBPersistenceLayer
|
||||||
|
The persistence layer instance responsible for direct DynamoDB operations.
|
||||||
|
exception_cls: Type[Exception], optional
|
||||||
|
The exception class to be raised when a requested item is not found.
|
||||||
|
tz: str, optional
|
||||||
|
The timezone identifier used for date/time operations.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
@@ -781,6 +829,8 @@ class DynamoDBCollection:
|
|||||||
head, tail = {}, items
|
head, tail = {}, items
|
||||||
|
|
||||||
def _getin(sk: SortKey, v: dict) -> dict:
|
def _getin(sk: SortKey, v: dict) -> dict:
|
||||||
|
from glom import glom
|
||||||
|
|
||||||
v = omit((PK, SK), v)
|
v = omit((PK, SK), v)
|
||||||
return glom(v, sk.path_spec) if sk.path_spec else v
|
return glom(v, sk.path_spec) if sk.path_spec else v
|
||||||
|
|
||||||
|
|||||||
@@ -165,9 +165,11 @@ if TYPE_CHECKING:
|
|||||||
CnpjStr = Annotated[str, ...]
|
CnpjStr = Annotated[str, ...]
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class CpfStr(CpfCnpj): ...
|
class CpfStr(CpfCnpj):
|
||||||
|
pass
|
||||||
|
|
||||||
class CnpjStr(CpfCnpj): ...
|
class CnpjStr(CpfCnpj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -7,14 +7,56 @@ def pick(
|
|||||||
exclude_none: bool = True,
|
exclude_none: bool = True,
|
||||||
default: Any = None,
|
default: Any = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Returns a partial copy of an object containing only the keys specified."""
|
"""
|
||||||
return {
|
Return a partial dict with only the specified keys.
|
||||||
k: dct.get(k, default) for k in keys if k in dct or not exclude_none
|
|
||||||
}
|
Parameters
|
||||||
|
----------
|
||||||
|
keys: list[str] or tuple[str, ...]
|
||||||
|
Keys to select.
|
||||||
|
dct: dict[str, Any]
|
||||||
|
Source dict.
|
||||||
|
exclude_none: bool, optional
|
||||||
|
If True, omit keys not in dct; if False, include them with `default`.
|
||||||
|
default: Any, optional
|
||||||
|
Value for keys not in dct when exclude_none is False.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[str, Any]
|
||||||
|
A dict with the picked key/value pairs.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> d = {'a': 1, 'b': 2, 'c': 3}
|
||||||
|
>>> pick(['a', 'c'], d)
|
||||||
|
{'a': 1, 'c': 3}
|
||||||
|
>>> pick(['a', 'd'], d, exclude_none=False, default='missing')
|
||||||
|
{'a': 1, 'd': 'missing'}
|
||||||
|
"""
|
||||||
|
return {k: dct.get(k, default) for k in keys if k in dct or not exclude_none}
|
||||||
|
|
||||||
|
|
||||||
def omit(
|
def omit(keys: list[str] | tuple[str, ...], dct: dict[str, Any]) -> dict[str, Any]:
|
||||||
keys: list[str] | tuple[str, ...], dct: dict[str, Any]
|
"""
|
||||||
) -> dict[str, Any]:
|
Return a partial dict omitting the specified keys.
|
||||||
"""Returns a partial copy of an object omitting the keys specified."""
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
keys: list[str] or tuple[str, ...]
|
||||||
|
Keys to omit.
|
||||||
|
dct: dict[str, Any]
|
||||||
|
Source dict.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[str, Any]
|
||||||
|
A dict without the omitted key/value pairs.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> d = {'a': 1, 'b': 2, 'c': 3}
|
||||||
|
>>> omit(['b'], d)
|
||||||
|
{'a': 1, 'c': 3}
|
||||||
|
"""
|
||||||
return {k: dct[k] for k in dct.keys() if k not in keys}
|
return {k: dct[k] for k in dct.keys() if k not in keys}
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ plugins:
|
|||||||
docstring_style: numpy
|
docstring_style: numpy
|
||||||
nav:
|
nav:
|
||||||
- index.md
|
- index.md
|
||||||
- API reference: reference.md
|
- DynamoDB: dynamodb.md
|
||||||
|
- API: api.md
|
||||||
|
|||||||
2
layercake/uv.lock
generated
2
layercake/uv.lock
generated
@@ -600,7 +600,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
|
|||||||
Reference in New Issue
Block a user