fix enroll and reenroll relationship

This commit is contained in:
2025-10-14 18:11:24 -03:00
parent 466ff824dd
commit a7e5a0a528
20 changed files with 125 additions and 89 deletions

View File

@@ -1,4 +1,7 @@
from abc import ABC
from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from enum import Enum
from typing import NotRequired, Self, TypedDict from typing import NotRequired, Self, TypedDict
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
@@ -32,15 +35,16 @@ Subscription = TypedDict(
) )
class LinkedEntity(str): class Kind(str, Enum):
def __new__(cls, id: str, type: str) -> Self: ORDER = 'ORDER'
return super().__new__(cls, '#'.join([type.lower(), id])) ENROLLMENT = 'ENROLLMENT'
def __init__(self, id: str, type: str) -> None:
# __init__ is used to store the parameters for later reference. @dataclass(frozen=True)
# For immutable types like str, __init__ cannot change the instance's value. class LinkedEntity(ABC):
self.id = id id: str
self.type = type kind: Kind
table_name: str | None = None
class DeduplicationConflictError(Exception): class DeduplicationConflictError(Exception):
@@ -76,29 +80,27 @@ def enroll(
) )
# Relationships between this enrollment and its related entities # Relationships between this enrollment and its related entities
for parent_entity in linked_entities: for entity in linked_entities:
perent_id = parent_entity.id
entity_sk = f'LINKED_ENTITIES#{parent_entity.type}'
keyprefix = parent_entity.type.lower()
# Parent knows the child # Parent knows the child
transact.put( transact.put(
item={ item={
'id': perent_id, 'id': entity.id,
'sk': f'{entity_sk}#CHILD', 'sk': f'LINKED_ENTITIES#{entity.kind.value}#CHILD',
'created_at': now_, 'created_at': now_,
f'{keyprefix}_id': enrollment.id, 'enrollment_id': enrollment.id,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
table_name=entity.table_name,
) )
keyprefix = entity.kind.value.lower()
# Child knows the parent # Child knows the parent
transact.put( transact.put(
item={ item={
'id': enrollment.id, 'id': enrollment.id,
'sk': f'{entity_sk}#PARENT', 'sk': f'LINKED_ENTITIES#{entity.kind.value}#PARENT',
'created_at': now_, 'created_at': now_,
f'{keyprefix}_id': perent_id, f'{keyprefix}_id': entity.id,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
) )

View File

@@ -81,6 +81,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
class Course: class Course:
id: str id: str
name: str name: str
access_period: int
def _get_courses(ids: set) -> tuple[Course, ...]: def _get_courses(ids: set) -> tuple[Course, ...]:
@@ -93,6 +94,7 @@ def _get_courses(ids: set) -> tuple[Course, ...]:
Course( Course(
id=idx, id=idx,
name=obj['name'], name=obj['name'],
access_period=obj['access_period'],
) )
for idx, obj in result.items() for idx, obj in result.items()
) )

View File

@@ -18,7 +18,11 @@ from layercake.dynamodb import (
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
from enrollment import LinkedEntity, enroll from enrollment import (
Kind,
LinkedEntity,
enroll,
)
from schemas import Course, Enrollment, User from schemas import Course, Enrollment, User
logger = Logger(__name__) logger = Logger(__name__)
@@ -89,7 +93,15 @@ def _handler(record: Course, context: dict) -> Enrollment:
enrollment, enrollment,
persistence_layer=enrollment_layer, persistence_layer=enrollment_layer,
deduplication_window={'offset_days': 90}, deduplication_window={'offset_days': 90},
linked_entities=frozenset({LinkedEntity(context['order_id'], 'ORDER')}), linked_entities=frozenset(
{
LinkedEntity(
id=context['order_id'],
kind=Kind.ORDER,
table_name=ORDER_TABLE,
),
}
),
) )
return enrollment return enrollment

View File

@@ -56,6 +56,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
) )
try: try:
if 's3_uri' not in cert:
raise ValueError('Template URI is missing')
# Send template URI and data to Paperforge API to generate a PDF # Send template URI and data to Paperforge API to generate a PDF
r = requests.post( r = requests.post(
PAPERFORGE_API, PAPERFORGE_API,
@@ -79,10 +82,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
), ),
}, },
), ),
timeout=5,
) )
r.raise_for_status() r.raise_for_status()
object_key = f'issuedcerts/{enrollment_id}.pdf' object_key = f'certs/{enrollment_id}.pdf'
s3_uri = f's3://{BUCKET_NAME}/{object_key}' s3_uri = f's3://{BUCKET_NAME}/{object_key}'
s3_client.put_object( s3_client.put_object(
@@ -93,10 +97,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
) )
logger.debug(f'PDF uploaded successfully to {s3_uri}') logger.debug(f'PDF uploaded successfully to {s3_uri}')
except KeyError: except ValueError as exc:
# PDF generation fails if template URI is missing # PDF generation fails if template URI is missing
s3_uri = None s3_uri = None
logger.debug('Template URI is missing') logger.exception(exc)
except requests.exceptions.RequestException as exc: except requests.exceptions.RequestException as exc:
logger.exception(exc) logger.exception(exc)
raise raise

View File

@@ -10,7 +10,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, SortKey, TransactKey
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE from config import ENROLLMENT_TABLE
from enrollment import LinkedEntity, enroll from enrollment import Kind, LinkedEntity, enroll
from schemas import Course, Enrollment, User from schemas import Course, Enrollment, User
logger = Logger(__name__) logger = Logger(__name__)
@@ -52,7 +52,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'offset_days': metadata['dedup_window_offset_days'], 'offset_days': metadata['dedup_window_offset_days'],
}, },
linked_entities=frozenset( linked_entities=frozenset(
{LinkedEntity(new_image['id'], 'ENROLLMENT')}, {
LinkedEntity(
id=new_image['id'],
kind=Kind.ENROLLMENT,
),
},
), ),
persistence_layer=dyn, persistence_layer=dyn,
) )

View File

@@ -29,8 +29,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
pk=old_image['id'], pk=old_image['id'],
sk='0', sk='0',
), ),
update_expr='SET access_expired = :true, \ update_expr='SET access_expired = :true, updated_at = :now',
updated_at = :now',
expr_attr_values={ expr_attr_values={
':true': True, ':true': True,
':now': now_, ':now': now_,

View File

@@ -308,6 +308,7 @@ Resources:
Type: AWS::Serverless::Function Type: AWS::Serverless::Function
Properties: Properties:
Handler: events.issue_cert.lambda_handler Handler: events.issue_cert.lambda_handler
# Timeout: 30
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies:
@@ -331,10 +332,10 @@ Resources:
old_image: old_image:
status: [IN_PROGRESS] status: [IN_PROGRESS]
EventCertReportingAppendCertFunction: EventReportingAppendCertFunction:
Type: AWS::Serverless::Function Type: AWS::Serverless::Function
Properties: Properties:
Handler: events.cert_reporting.append_cert.lambda_handler Handler: events.reporting.append_cert.lambda_handler
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies:
@@ -346,23 +347,19 @@ Resources:
Properties: Properties:
Pattern: Pattern:
resources: [!Ref EnrollmentTable] resources: [!Ref EnrollmentTable]
detail-type: [MODIFY]
detail: detail:
keys: keys:
sk: ["0"] sk: ["0"]
new_image: new_image:
status: [COMPLETED] status: [COMPLETED]
cert:
exists: true
org_id: org_id:
exists: true - exists: true
old_image:
cert:
exists: false
EventCertReportingSendReportEmailFunction: EventReportingSendReportEmailFunction:
Type: AWS::Serverless::Function Type: AWS::Serverless::Function
Properties: Properties:
Handler: events.cert_reporting.send_report_email.lambda_handler Handler: events.reporting.send_report_email.lambda_handler
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies:

View File

@@ -20,6 +20,7 @@ def pytest_configure():
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['BUCKET_NAME'] = 'saladeaula.digital' os.environ['BUCKET_NAME'] = 'saladeaula.digital'
os.environ['LOG_LEVEL'] = 'DEBUG'
@dataclass @dataclass

View File

@@ -1,6 +1,6 @@
from datetime import timedelta from datetime import timedelta
import app.events.cert_reporting.append_cert as app import app.events.reporting.append_cert as app
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 ( from layercake.dynamodb import (

View File

@@ -1,4 +1,4 @@
import app.events.cert_reporting.send_report_email as app import app.events.reporting.send_report_email as app
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,

View File

@@ -1,6 +1,6 @@
import app.events.enroll as app import app.events.enroll as app
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
def test_enroll( def test_enroll(
@@ -8,12 +8,26 @@ def test_enroll(
dynamodb_persistence_layer: DynamoDBPersistenceLayer, dynamodb_persistence_layer: DynamoDBPersistenceLayer,
lambda_context: LambdaContext, lambda_context: LambdaContext,
): ):
order_id = 'cpYSbBcie2NDbZhDKCxCih'
event = { event = {
'detail': { 'detail': {
'new_image': { 'new_image': {
'id': 'cpYSbBcie2NDbZhDKCxCih', 'id': order_id,
'sk': 'generated_items', 'sk': 'generated_items',
} }
} }
} }
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore
# Parent knows the child
order_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair(order_id, 'LINKED_ENTITIES#ORDER#CHILD')
)
assert order_entity
# Child knows the parent
enrollment_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair(order_entity['enrollment_id'], 'LINKED_ENTITIES#ORDER#PARENT'),
)
assert enrollment_entity['order_id'] == order_id

View File

@@ -30,19 +30,19 @@ def test_reenroll(
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore
# Parent knows the child # Parent knows the child
child_entity = dynamodb_persistence_layer.collection.get_item( current_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair( KeyPair(
pk=parent_id, pk=parent_id,
sk='LINKED_ENTITIES#ENROLLMENT#CHILD', sk='LINKED_ENTITIES#ENROLLMENT#CHILD',
) )
) )
assert child_entity assert current_entity
# Child knows the parent # Child knows the parent
parent_entity = dynamodb_persistence_layer.collection.get_item( new_entity = dynamodb_persistence_layer.collection.get_item(
KeyPair( KeyPair(
pk=child_entity['enrollment_id'], pk=current_entity['enrollment_id'],
sk='LINKED_ENTITIES#ENROLLMENT#PARENT', sk='LINKED_ENTITIES#ENROLLMENT#PARENT',
) )
) )
assert parent_entity['enrollment_id'] == parent_id assert new_entity['enrollment_id'] == parent_id

View File

@@ -1,4 +1,5 @@
import urllib.parse as parse import urllib.parse as parse
from os import rename
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 (
@@ -73,16 +74,16 @@ def get_enrollment(id: str):
record = enrollment_layer.collection.get_items( record = enrollment_layer.collection.get_items(
TransactKey(id) TransactKey(id)
+ SortKey('0') + SortKey('0')
+ SortKey('ORG', rename_key='org')
# + SortKey('STARTED', rename_key='started_at', path_spec='started_at') # + SortKey('STARTED', rename_key='started_at', path_spec='started_at')
# + SortKey('COMPLETED', rename_key='completed_at', path_spec='completed_at') # + SortKey('COMPLETED', rename_key='completed_at', path_spec='completed_at')
# + SortKey('FAILED', rename_key='failed_at', path_spec='failed_at') # + SortKey('FAILED', rename_key='failed_at', path_spec='failed_at')
+ SortKey('CANCELED', rename_key='canceled') + SortKey('CANCELED', rename_key='canceled')
+ SortKey('ARCHIVED', rename_key='archived_at', path_spec='archived_at') # + SortKey('ARCHIVED', rename_key='archived_at', path_spec='archived_at')
+ SortKey('CANCEL_POLICY', rename_key='cancel_policy') + SortKey('CANCEL_POLICY', rename_key='cancel_policy')
+ SortKey('LOCK', rename_key='lock') + SortKey('LOCK', rename_key='lock')
+ SortKey('parent_vacancy', path_spec='vacancy') + SortKey('parent_vacancy', path_spec='vacancy')
+ SortKey('author') + SortKey('author')
+ SortKey('tenant')
) )
events = enrollment_layer.collection.query(KeyPair(id, 'SCHEDULE#')) events = enrollment_layer.collection.query(KeyPair(id, 'SCHEDULE#'))

View File

@@ -14,7 +14,7 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
def download(id: str): def download(id: str):
params = { params = {
'Bucket': BUCKET_NAME, 'Bucket': BUCKET_NAME,
'Key': f'issuedcerts/{id}.pdf', 'Key': f'certs/{id}.pdf',
'ResponseContentDisposition': f'attachment; filename="{id}.pdf"', 'ResponseContentDisposition': f'attachment; filename="{id}.pdf"',
} }

View File

@@ -55,7 +55,7 @@ def update_progress(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr='SET progress = :progress, \ update_expr='SET progress = :progress, \
#status = :in_progress, \ #status = :in_progress, \
started_at = :now, \ started_at = if_not_exists(started_at, :now), \
updated_at = :now', updated_at = :now',
cond_expr='#status = :pending', cond_expr='#status = :pending',
expr_attr_names={ expr_attr_names={
@@ -69,15 +69,6 @@ def update_progress(
}, },
exc_cls=EnrollmentConflictError, exc_cls=EnrollmentConflictError,
) )
# Record the start date if it does not already exist
transact.put(
item={
'id': id,
'sk': 'STARTED',
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
)
# Schedule a reminder for inactivity # Schedule a reminder for inactivity
transact.put( transact.put(
item={ item={
@@ -192,7 +183,7 @@ def _set_status_as_completed(
update_expr='SET #status = :completed, \ update_expr='SET #status = :completed, \
progress = :progress, \ progress = :progress, \
score = :score, \ score = :score, \
completed_at = :now, \ completed_at = if_not_exists(completed_at, :now), \
updated_at = :now', updated_at = :now',
cond_expr='#status = :in_progress', cond_expr='#status = :in_progress',
expr_attr_names={'#status': 'status'}, expr_attr_names={'#status': 'status'},
@@ -205,14 +196,6 @@ def _set_status_as_completed(
}, },
exc_cls=EnrollmentConflictError, exc_cls=EnrollmentConflictError,
) )
transact.put(
item={
'id': id,
'sk': 'COMPLETED',
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
)
if cert_exp_interval: if cert_exp_interval:
transact.put( transact.put(
@@ -293,7 +276,7 @@ def _set_status_as_failed(
progress = :progress, \ progress = :progress, \
score = :score, \ score = :score, \
access_expired = :true, \ access_expired = :true, \
failed_at = :now, \ failed_at = if_not_exists(failed_at, :now), \
updated_at = :now', updated_at = :now',
cond_expr='#status = :in_progress', cond_expr='#status = :in_progress',
expr_attr_names={'#status': 'status'}, expr_attr_names={'#status': 'status'},

View File

@@ -33,12 +33,13 @@ def test_start_progress(
r = dynamodb_persistence_layer.collection.query( r = dynamodb_persistence_layer.collection.query(
PartitionKey('d9da85f2-e09f-472d-9515-3d91d70f1e8a') PartitionKey('d9da85f2-e09f-472d-9515-3d91d70f1e8a')
) )
assert any(item.get('sk') == 'STARTED' for item in r['items']) docx = next((x for x in r['items'] if x['sk'] == '0'), {})
assert 'started_at' in docx
assert any( assert any(
item.get('sk') == 'SCHEDULE#REMINDER_NO_ACTIVITY_AFTER_7_DAYS' item.get('sk') == 'SCHEDULE#REMINDER_NO_ACTIVITY_AFTER_7_DAYS'
for item in r['items'] for item in r['items']
) )
assert len(r['items']) == 3 assert len(r['items']) == 2
def test_update_progress( def test_update_progress(
@@ -110,8 +111,9 @@ def test_set_as_completed(
PartitionKey('6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8') PartitionKey('6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8')
) )
assert len(r['items']) == 8 docx = next((x for x in r['items'] if x['sk'] == '0'), {})
assert any(item.get('sk') == 'COMPLETED' for item in r['items']) assert 'completed_at' in docx
assert len(r['items']) == 7
assert any(item.get('sk') == 'LOCK' for item in r['items']) assert any(item.get('sk') == 'LOCK' for item in r['items'])
assert any( assert any(
item.get('sk') == 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS' item.get('sk') == 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS'

46
konviva-events/uv.lock generated
View File

@@ -31,14 +31,14 @@ wheels = [
[[package]] [[package]]
name = "authlib" name = "authlib"
version = "1.6.1" version = "1.6.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cryptography" }, { name = "cryptography" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" } sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" }, { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" },
] ]
[[package]] [[package]]
@@ -430,6 +430,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
] ]
[[package]]
name = "joserfc"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/a0/4b8dfecc8ec3c15aa1f2ff7d5b947344378b5b595ce37c8a8fe6e25c1400/joserfc-1.4.0.tar.gz", hash = "sha256:e8c2f327bf10a937d284d57e9f8aec385381e5e5850469b50a7dade1aba59759", size = 196339, upload-time = "2025-10-09T07:47:00.835Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/05/342459b7629c6fcb5f99a646886ee2904491955b8cce6b26b0b9a498f67c/joserfc-1.4.0-py3-none-any.whl", hash = "sha256:46917e6b53f1ec0c7e20d34d6f3e6c27da0fa43d0d4ebfb89aada7c86582933a", size = 66390, upload-time = "2025-10-09T07:46:59.591Z" },
]
[[package]] [[package]]
name = "jsonlines" name = "jsonlines"
version = "4.0.0" version = "4.0.0"
@@ -485,7 +497,7 @@ dev = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.9.14" version = "0.10.1"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },
@@ -494,6 +506,7 @@ dependencies = [
{ name = "dictdiffer" }, { name = "dictdiffer" },
{ name = "ftfy" }, { name = "ftfy" },
{ name = "glom" }, { name = "glom" },
{ name = "joserfc" },
{ name = "meilisearch" }, { name = "meilisearch" },
{ name = "orjson" }, { name = "orjson" },
{ name = "passlib" }, { name = "passlib" },
@@ -501,7 +514,7 @@ dependencies = [
{ name = "pycpfcnpj" }, { name = "pycpfcnpj" },
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "pyjwt" }, { name = "python-multipart" },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "smart-open", extra = ["s3"] }, { name = "smart-open", extra = ["s3"] },
@@ -512,11 +525,12 @@ dependencies = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "arnparse", specifier = ">=0.0.2" }, { name = "arnparse", specifier = ">=0.0.2" },
{ name = "authlib", specifier = ">=1.6.1" }, { name = "authlib", specifier = ">=1.6.5" },
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" }, { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
{ name = "dictdiffer", specifier = ">=0.9.0" }, { name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "ftfy", specifier = ">=6.3.1" }, { name = "ftfy", specifier = ">=6.3.1" },
{ name = "glom", specifier = ">=24.11.0" }, { name = "glom", specifier = ">=24.11.0" },
{ name = "joserfc", specifier = ">=1.2.2" },
{ name = "meilisearch", specifier = ">=0.34.0" }, { name = "meilisearch", specifier = ">=0.34.0" },
{ name = "orjson", specifier = ">=3.10.15" }, { name = "orjson", specifier = ">=3.10.15" },
{ name = "passlib", specifier = ">=1.7.4" }, { name = "passlib", specifier = ">=1.7.4" },
@@ -524,7 +538,7 @@ requires-dist = [
{ name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pycpfcnpj", specifier = ">=1.8" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
{ name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "pyjwt", specifier = ">=2.10.1" }, { name = "python-multipart", specifier = ">=0.0.20" },
{ name = "pytz", specifier = ">=2025.1" }, { name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" }, { name = "requests", specifier = ">=2.32.3" },
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
@@ -761,15 +775,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
] ]
[[package]]
name = "pyjwt"
version = "2.10.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.4.1" version = "8.4.1"
@@ -821,6 +826,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
] ]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"