From e33eccebb9c6c43991343b1ce911422ec3c0755a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Tue, 26 Aug 2025 00:00:42 -0300 Subject: [PATCH] add postgres --- README.md | 24 +- enrollments-events/app/enrollment.py | 94 +---- .../app/events/emails/email_.py | 26 +- .../reminder_access_period_before_30_days.py | 2 +- ...reminder_cert_expiration_before_30_days.py | 2 +- .../emails/reminder_no_access_after_3_days.py | 2 +- .../reminder_no_activity_after_7_days.py | 2 +- enrollments-events/app/events/issue_cert.py | 6 +- .../app/events/reenroll_if_failed.py | 13 +- .../app/events/schedule_reminders.py | 4 +- .../events/stopgap/patch_course_metadata.py | 12 +- enrollments-events/template.yaml | 75 ++-- .../stopgap/test_patch_course_metadata.py | 8 +- enrollments-events/tests/seeds.jsonl | 1 + http-api/app/routes/enrollments/enroll.py | 1 - http-api/app/rules/enrollment.py | 42 --- http-api/compose.yaml | 14 + http-api/pyproject.toml | 2 + http-api/template.yaml | 2 +- http-api/uv.lock | 133 +++---- konviva-events/app/boto3clients.py | 10 +- konviva-events/app/events/create_user.py | 4 +- konviva-events/app/events/enroll.py | 1 + konviva-events/app/konviva.py | 8 +- konviva-events/pyproject.toml | 1 + konviva-events/template.yaml | 77 +++-- konviva-events/tests/conftest.py | 104 +++++- .../tests/events/test_create_user.py | 5 +- konviva-events/tests/events/test_enroll.py | 5 +- .../tests/events/test_update_user.py | 2 +- konviva-events/tests/seeds.jsonl | 34 +- konviva-events/uv.lock | 327 ++++-------------- layercake/pyproject.toml | 3 +- layercake/uv.lock | 60 +++- streams-events/app/config.py | 6 + .../app/events/docs_into_eventbus.py | 1 + .../app/events/index_docs_into_meili.py | 3 +- .../app/events/replicate_into_meili.py | 33 -- streams-events/pyproject.toml | 7 + streams-events/template.yaml | 32 +- streams-events/tests/conftest.py | 5 + .../tests/test_replicate_into_meili.py | 5 - streams-events/uv.lock | 60 +++- 43 files changed, 622 insertions(+), 636 deletions(-) delete mode 100644 streams-events/app/events/replicate_into_meili.py delete mode 100644 streams-events/tests/test_replicate_into_meili.py diff --git a/README.md b/README.md index bcbc3f6..65b776c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ O gestor responsável pela ação também é relacionado à compra, com base no ```json {"id": "101", "sk": "0", "name": "EDUSEG", "cnpj": "15608435000190", "org_id": "100"} -{"id": "101", "sk": "AUTHOR", "name": "Sérgio", "email": "sergio@somosbeta.com.br", "user_id": "123"} +{"id": "101", "sk": "CREATED_BY", "name": "Sérgio", "email": "sergio@somosbeta.com.br", "user_id": "123"} {"id": "101", "sk": "SLOT", "status": "PENDING", "mode": "BATCH"} {"id": "101", "sk": "SLOT#ENROLLMENT#9omWNKymwU5U4aeun6mWzZ", "status": "SUCCESS"} {"id": "101", "sk": "SLOT#ENROLLMENT#12", "status": "ROLLBACK"} @@ -37,8 +37,9 @@ Quando o responsável é uma pessoa física (CPF). ```json {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "0", "course": {"id": "10", "name": "pytest"}, "org_id": "100"} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}} -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "OWNER_ORG", "org_id": "100", "name": "EDUSEG"} -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "AUTHOR", "user_id": "202", "name": "Tiago Maciel"} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "ORG", "org_id": "100", "name": "EDUSEG"} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "CREATED_BY", "user_id": "202", "name": "Tiago Maciel"} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "KONVIVA", "user_id": 122, "class_id": 123, "enrollment_id": 1239} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "STARTED", "started_at": "2025-04-06T11:07:32.762178-03:00"} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "COMPLETED", "completed_at": "2025-04-06T11:07:32.762178-03:00"} @@ -47,6 +48,11 @@ Quando o responsável é uma pessoa física (CPF). {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "ARCHIVED", "archived_at": "2025-04-06T11:07:32.762178-03:00"} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "EXPIRED", "expired_at": "2025-04-06T11:07:32.762178-03:00"} {"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "LINKED_ENTITIES#ORDER", "order_id": "101"} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "LOCK", "hash": "1e67e29464877783e49e07fb7d9dd372", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDUE#REMINDER_NO_ACCESS_AFTER_3_DAYS", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDUE#REMINDER_NO_ACTIVITY_AFTER_7_DAYS", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDUE#REMINDER_ACCESS_PERIOD_BEFORE_15_DAYS", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDUE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS", "ttl": 1874507093} ``` ### Vagas disponíveis @@ -62,21 +68,21 @@ Quando uma matrícula é criada, também é agendados emails/eventos. - `REMINDER_NO_ACCESS_AFTER_3_DAYS` se o usuário não acessar o curso 3 dias após a criação. - `REMINDER_NO_ACTIVITY_AFTER_7_DAYS` 7 dias após a última atividade do usuário no curso. -- `REMINDER_ACCESS_PERIOD_BEFORE_15_DAYS` 30 dias antes do período de acesso ao curso terminar. +- `REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS` 30 dias antes do período de acesso ao curso terminar. - `REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS` se houver certificado, avisa 30 dias antes do certificado expirar. - `SET_AS_ARCHIVED` após o certificado expirar, a matrícula será marcada como **arquivada (ARCHIVED)**. - `SET_AS_EXPIRED` se não houver certificado e o período de acesso for atingido, a matrícula será marcada com **expirada (EXPIRED)**. ```json -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULES#REMINDER_NO_ACCESS_3_DAYS", "name": "Sérgio R Siqueira", "email": "osergiosiqueira@gmail.com", "ttl": 1874507093} -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULES#COURSE_EXPIRED", "name": "Sérgio R Siqueira", "email": "osergiosiqueira@gmail.com", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULE#REMINDER_NO_ACCESS_3_DAYS", "name": "Sérgio R Siqueira", "email": "osergiosiqueira@gmail.com", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULE#SET_AS_EXPIRED", "name": "Sérgio R Siqueira", "email": "osergiosiqueira@gmail.com", "ttl": 1874507093} ``` -Quando o status da matrícula for alterado para `COMPLETED`, os eventos `COURSE_EXPIRED` e `ACCESS_PERIOD_REMINDER_30_DAYS` serão removidos a adicionado o evento `COURSE_ARCHIVED`. +Quando o status da matrícula for alterado para `COMPLETED`, os eventos `SET_AS_EXPIRED` e `REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS` serão _removidos_ a adicionado o evento `SET_AS_ARCHIVED`. ```json -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULES#REMINDER_NO_ACCESS_3_DAYS", "name": "Sérgio R Siqueira", "email": "osergiosiqueira@gmail.com", "ttl": 1874507093} -{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULES#COURSE_EXPIRED", "name": "Sérgio R Siqueira", "email": "osergiosiqueira@gmail.com", "ttl": 1874507093} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "0", ...} +{"id": "9omWNKymwU5U4aeun6mWzZ", "sk": "SCHEDULE#SET_AS_ARCHIVED", "ttl": 1874507093} ``` ### Proteção contra duplicação diff --git a/enrollments-events/app/enrollment.py b/enrollments-events/app/enrollment.py index 513b07f..f4d4017 100644 --- a/enrollments-events/app/enrollment.py +++ b/enrollments-events/app/enrollment.py @@ -1,6 +1,4 @@ from dataclasses import asdict, dataclass -from datetime import timedelta -from enum import Enum from typing import Self, TypedDict from layercake.dateutils import now, ttl @@ -36,35 +34,6 @@ class Slot: return LinkedEntity(idx, 'ORDER') -class LifecycleEvents(str, Enum): - """Lifecycle events related to scheduling actions.""" - - # Reminder if the user does not access within 3 days - # REMINDER_NO_ACCESS_AFTER_3_DAYS = 'SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS' - DOES_NOT_ACCESS = 'schedules#does_not_access' - - # When there is no activity 7 days after the first access - # REMINDER_NO_ACTIVITY_AFTER_7_DAYS = 'SCHEDULE#REMINDER_NO_ACTIVITY_AFTER_7_DAYS' - NO_ACTIVITY = 'schedules#no_activity' - - # Reminder 30 days before the access period expires - # REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS = 'SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS' - ACCESS_PERIOD_ENDS = 'schedules#access_period_ends' - - # Reminder for certificate expiration set to 30 days from now - REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS = ( - 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS' - ) - - # Archive the course after the certificate expires - # SET_AS_ARCHIVE = 'SCHEDULE#SET_AS_ARCHIVE' - ARCHIVE_IT = 'schedules#archive_it' - - # When the access period ends for a course without a certificate - # SET_AS_EXPIRE = 'SCHEDULE#SET_AS_EXPIRE' - EXPIRATION = 'schedules#expiration' - - class DeduplicationConflictError(Exception): def __init__(self, *args): super().__init__('Enrollment already exists') @@ -105,53 +74,11 @@ def enroll( transact.put( item={ 'id': enrollment.id, - 'sk': 'metadata#course', + 'sk': 'METADATA#COURSE', 'created_at': now_, **course.model_dump(include={'cert', 'access_period'}), } ) - transact.put( - item={ - 'id': enrollment.id, - # Post-migration: uncomment the following line - # 'sk': LifecycleEvents.REMINDER_NO_ACCESS_3_DAYS, - 'sk': LifecycleEvents.DOES_NOT_ACCESS, - 'name': user.name, - 'email': user.email, - 'course': course.name, - 'created_at': now_, - 'ttl': ttl(days=3, start_dt=now_), - }, - ) - # Enrollment expires by default when the access period ends. - # When the course is completed, it is automatically removed, - # and the `SCHEDULE#SET_AS_ARCHIVE` event is created. - transact.put( - item={ - 'id': enrollment.id, - 'sk': LifecycleEvents.EXPIRATION, - # Post-migration: uncomment the following line - # 'sk': LifecycleEvents.COURSE_EXPIRED, - 'name': user.name, - 'email': user.email, - 'course': course.name, - 'created_at': now_, - 'ttl': ttl(start_dt=now_ + timedelta(days=course.access_period)), - }, - ) - transact.put( - item={ - 'id': enrollment.id, - # Post-migration: uncomment the following line - # 'sk': LifecycleEvents.ACCESS_PERIOD_REMINDER_30_DAYS, - 'sk': LifecycleEvents.ACCESS_PERIOD_ENDS, - 'name': user.name, - 'email': user.email, - 'course': course.name, - 'created_at': now_, - 'ttl': ttl(start_dt=now_ + timedelta(days=course.access_period - 30)), - }, - ) for entity in linked_entities: keyprefix = entity.type.lower() @@ -184,7 +111,7 @@ def enroll( transact.put( item={ 'id': enrollment.id, - 'sk': 'cancel_policy', + 'sk': 'CANCEL_POLICY', 'created_at': now_, } ) @@ -204,16 +131,17 @@ def enroll( # the deduplication window expires or is removed. if deduplication_window: offset_days = deduplication_window['offset_days'] - ttl_expiration = ttl( - start_dt=now_ + timedelta(days=course.access_period - offset_days) + ttl_ = ttl( + start_dt=now_, + days=course.access_period - offset_days, ) transact.put( item={ - 'id': 'lock', + 'id': 'LOCK', 'sk': lock_hash, 'enrollment_id': enrollment.id, 'created_at': now_, - 'ttl': ttl_expiration, + 'ttl': ttl_, }, cond_expr='attribute_not_exists(sk)', exc_cls=DeduplicationConflictError, @@ -221,24 +149,24 @@ def enroll( transact.put( item={ 'id': enrollment.id, - 'sk': 'lock', + 'sk': 'LOCK', 'hash': lock_hash, 'created_at': now_, - 'ttl': ttl_expiration, + 'ttl': ttl_, }, ) # Deduplication window can be recalculated if needed transact.put( item={ 'id': enrollment.id, - 'sk': 'metadata#deduplication_window', + 'sk': 'METADATA#DEDUPLICATION_WINDOW', 'offset_days': offset_days, 'created_at': now_, }, ) else: transact.condition( - key=KeyPair('lock', lock_hash), + key=KeyPair('LOCK', lock_hash), cond_expr='attribute_not_exists(sk)', exc_cls=DeduplicationConflictError, ) diff --git a/enrollments-events/app/events/emails/email_.py b/enrollments-events/app/events/emails/email_.py index e5f2562..5838793 100644 --- a/enrollments-events/app/events/emails/email_.py +++ b/enrollments-events/app/events/emails/email_.py @@ -32,10 +32,15 @@ def send_email( emailmsg = Message( from_=sender, to=to, - subject=subject.format(course=truncate_str(context['course'])), + subject=subject.format( + course=truncate_str(context['course']), + ), ) emailmsg.add_alternative( - message.format(first_name=first_word(name), course=context['course']) + message.format( + first_name=first_word(name), + course=context['course'], + ) ) try: @@ -46,6 +51,13 @@ def send_email( }, } ) + dynamodb_persistence_layer.put_item( + item={ + 'id': event['id'], + 'sk': f'{event["sk"]}#EXECUTED', + 'created_at': now_, + } + ) logger.info('Email sent') except Exception as exc: logger.exception(exc) @@ -59,13 +71,5 @@ def send_email( ) return False - else: - dynamodb_persistence_layer.put_item( - item={ - 'id': event['id'], - 'sk': f'{event["sk"]}#EXECUTED', - 'created_at': now_, - } - ) - return True + return True diff --git a/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py b/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py index b43d828..121b30e 100644 --- a/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py +++ b/enrollments-events/app/events/emails/reminder_access_period_before_30_days.py @@ -18,7 +18,7 @@ logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) -SUBJECT = 'Seu acesso ao curso de {course} termina em 30 dias' +SUBJECT = 'Seu acesso ao curso {course} termina em 30 dias' MESSAGE = """ Oi {first_name}, tudo bem?

diff --git a/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py b/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py index 63acca9..1fd2b16 100644 --- a/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py +++ b/enrollments-events/app/events/emails/reminder_cert_expiration_before_30_days.py @@ -18,7 +18,7 @@ logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) -SUBJECT = 'Seu certificado de {course} está prestes a expirar' +SUBJECT = 'Seu certificado {course} vai expirar em breve' MESSAGE = """ Oi {first_name}, tudo bem?

diff --git a/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py b/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py index d96dfdd..093f058 100644 --- a/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py +++ b/enrollments-events/app/events/emails/reminder_no_access_after_3_days.py @@ -18,7 +18,7 @@ logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) -SUBJECT = 'Seu curso de {course} está esperando por você na EDUSEG®' +SUBJECT = 'Seu curso {course} está esperando por você na EDUSEG®' MESSAGE = """ Oi {first_name}, tudo bem?

diff --git a/enrollments-events/app/events/emails/reminder_no_activity_after_7_days.py b/enrollments-events/app/events/emails/reminder_no_activity_after_7_days.py index a8c81b1..f240333 100644 --- a/enrollments-events/app/events/emails/reminder_no_activity_after_7_days.py +++ b/enrollments-events/app/events/emails/reminder_no_activity_after_7_days.py @@ -18,7 +18,7 @@ logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) -SUBJECT = 'Seu curso de {course} está parado há 7 dias...' +SUBJECT = 'Seu curso {course} está parado há 7 dias...' MESSAGE = """ Oi {first_name}, tudo bem?

diff --git a/enrollments-events/app/events/issue_cert.py b/enrollments-events/app/events/issue_cert.py index 817507d..7290e06 100644 --- a/enrollments-events/app/events/issue_cert.py +++ b/enrollments-events/app/events/issue_cert.py @@ -4,7 +4,7 @@ from aws_lambda_powertools.utilities.data_classes import ( event_source, ) from aws_lambda_powertools.utilities.typing import LambdaContext -from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey +from layercake.dynamodb import DynamoDBPersistenceLayer, SortKey, TransactKey from boto3clients import dynamodb_client from config import ( @@ -21,8 +21,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] enrollment = enrollment_layer.collection.get_items( TransactKey(pk=new_image['id']) - + SortKey('METADATA#DEDUPLICATION_WINDOW', rename_key='DEDUPLICATION_WINDOW') - + SortKey('METADATA#COURSE', rename_key='COURSE'), + + SortKey('METADATA#DEDUPLICATION_WINDOW', rename_key='deduplication_window') + + SortKey('METADATA#COURSE', rename_key='course'), ) return True diff --git a/enrollments-events/app/events/reenroll_if_failed.py b/enrollments-events/app/events/reenroll_if_failed.py index 4fdb40a..ef12a9d 100644 --- a/enrollments-events/app/events/reenroll_if_failed.py +++ b/enrollments-events/app/events/reenroll_if_failed.py @@ -19,16 +19,11 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) @logger.inject_lambda_context def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] - subscription = enrollment_layer.collection.get_items( + data = enrollment_layer.collection.get_items( TransactKey(new_image['id']) - + SortKey('METADATA#SUBSCRIPTION_COVERED') - + SortKey('author', rename_key='CREATED_BY') - + SortKey('tenant', rename_key='ORG') - + SortKey('CREATED_BY') - + SortKey('ORG') + + SortKey('METADATA#SUBSCRIPTION_COVERED', rename_key='subscription') + + SortKey('author', rename_key='created_by') + + SortKey('tenant', rename_key='org') ) - with enrollment_layer.transact_writer() as transact_writer: - ... - return True diff --git a/enrollments-events/app/events/schedule_reminders.py b/enrollments-events/app/events/schedule_reminders.py index 0f61a44..b80c2b0 100644 --- a/enrollments-events/app/events/schedule_reminders.py +++ b/enrollments-events/app/events/schedule_reminders.py @@ -37,8 +37,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: 'ttl': ttl(days=3, start_dt=now_), }, ) + # By default, the enrollment will expire when the access period ends # (scheduled below). + # If the enrollment is completed earlier (e.g., certificate issued), # the expiration schedule is canceled and an archive schedule # (`SCHEDULE#SET_AS_ARCHIVED`) is created instead. @@ -53,7 +55,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: transact.put( item={ 'id': enrollment_id, - 'sk': 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS', + 'sk': 'SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS', 'name': user.name, 'email': user.email, 'course': course.name, diff --git a/enrollments-events/app/events/stopgap/patch_course_metadata.py b/enrollments-events/app/events/stopgap/patch_course_metadata.py index 78c6a22..855b759 100644 --- a/enrollments-events/app/events/stopgap/patch_course_metadata.py +++ b/enrollments-events/app/events/stopgap/patch_course_metadata.py @@ -8,11 +8,12 @@ from aws_lambda_powertools.utilities.data_classes import ( ) from aws_lambda_powertools.utilities.typing import LambdaContext from layercake.dateutils import now -from layercake.dynamodb import DynamoDBPersistenceLayer +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from sqlite_utils import Database from boto3clients import dynamodb_client from config import ( + COURSE_TABLE, ENROLLMENT_TABLE, SQLITE_DATABASE, SQLITE_TABLE, @@ -22,6 +23,7 @@ sqlite3.register_converter('json', json.loads) logger = Logger(__name__) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) +course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) @event_source(data_class=EventBridgeEvent) @@ -37,7 +39,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: transact.put( item={ 'id': new_image['id'], - 'sk': 'metadata#deduplication_window', + 'sk': 'METADATA#DEDUPLICATION_WINDOW', 'offset_days': 90, 'created_at': now_, } @@ -45,7 +47,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: transact.put( item={ 'id': new_image['id'], - 'sk': 'metadata#course', + 'sk': 'METADATA#COURSE', 'created_at': now_, 'access_period': int(course['access_period']), 'cert': { @@ -66,6 +68,10 @@ class CourseNotFoundError(Exception): def _get_course(course_id: str) -> dict: + course = course_layer.get_item(KeyPair(pk=course_id, sk='0')) + if course: + return course + with sqlite3.connect( database=SQLITE_DATABASE, detect_types=sqlite3.PARSE_DECLTYPES ) as conn: diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index 5a0123e..f73b929 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -76,6 +76,8 @@ Resources: Policies: - DynamoDBCrudPolicy: TableName: !Ref EnrollmentTable + - DynamoDBCrudPolicy: + TableName: !Ref CourseTable Events: DynamoDBEvent: Type: EventBridgeRule @@ -281,7 +283,7 @@ Resources: detail: keys: sk: - - SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS + - SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS # Post-migration: remove the following line - schedules#access_period_ends @@ -310,6 +312,8 @@ Resources: # Post-migration: remove the following line - schedules#expiration + # If there is no certificate and the access period has ended, + # the enrollment will be marked as expired EventSetAsExpiredFunction: Type: AWS::Serverless::Function Properties: @@ -328,28 +332,55 @@ Resources: detail-type: [EXPIRE] detail: keys: - sk: [SCHEDULE#SET_AS_EXPIRED] + sk: + - SCHEDULE#SET_AS_EXPIRED + # Post-migration: remove the following line + - schedules#access_period_ends - # EventScheduleRemindersFunction: - # Type: AWS::Serverless::Function - # Properties: - # Handler: events.schedule_reminders.lambda_handler - # LoggingConfig: - # LogGroup: !Ref EventLog - # Policies: - # - DynamoDBCrudPolicy: - # TableName: !Ref EnrollmentTable - # Events: - # DynamoDBEvent: - # Type: EventBridgeRule - # Properties: - # Pattern: - # resources: [!Ref EnrollmentTable] - # detail-type: [INSERT] - # detail: - # new_image: - # sk: ["0"] - # status: [PENDING] + # After the certificate expires, the enrollment will be marked as archived + EventSetAsArchivedFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.set_as_archived.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref EnrollmentTable + Events: + DynamoDBEvent: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref EnrollmentTable] + detail-type: [EXPIRE] + detail: + keys: + sk: + - SCHEDULE#SET_AS_ARCHIVED + # Post-migration: remove the following line + - schedules#archive_it + + EventScheduleRemindersFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.schedule_reminders.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref EnrollmentTable + Events: + DynamoDBEvent: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref EnrollmentTable] + detail-type: [INSERT] + detail: + new_image: + sk: ["0"] + status: [PENDING] EventIssueCertFunction: Type: AWS::Serverless::Function diff --git a/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py b/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py index 58b30a1..343ec54 100644 --- a/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py +++ b/enrollments-events/tests/events/stopgap/test_patch_course_metadata.py @@ -30,9 +30,9 @@ def test_enroll( result = dynamodb_persistence_layer.collection.get_items( TransactKey('47ZxxcVBjvhDS5TE98tpfQ') + SortKey('0') - + SortKey('metadata#deduplication_window') - + SortKey('metadata#course') + + SortKey('METADATA#DEDUPLICATION_WINDOW') + + SortKey('METADATA#COURSE') ) - assert 'metadata#course' in result - assert 'metadata#deduplication_window' in result + assert 'METADATA#COURSE' in result + assert 'METADATA#DEDUPLICATION_WINDOW' in result diff --git a/enrollments-events/tests/seeds.jsonl b/enrollments-events/tests/seeds.jsonl index f257de0..b7fcac6 100644 --- a/enrollments-events/tests/seeds.jsonl +++ b/enrollments-events/tests/seeds.jsonl @@ -14,6 +14,7 @@ {"id": "123", "sk": "0", "access_period": 360, "cert": {"exp_interval": 360}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "pytest", "tenant_id": "*"} {"id": "a955518e-ebcb-4441-b914-ddc9ecef84f0", "sk": "0", "access_period": "360", "cert": {"exp_interval": 360}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "NR-11 Operador de Munck", "tenant_id": "*"} {"id": "6a403773-aeac-4e6a-ac39-dc958e4be52a", "sk": "0", "access_period": "360", "cert": {"exp_interval": 360}, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id": "281", "name": "Reciclagem em NR-11 - Operador de Empilhadeira", "tenant_id": "*"} +{"id": "e1c44881-2fe3-484e-ada2-12b6bf5b9398", "sk": "0", "name": "NR-35 Segurança nos Trabalhos em Altura (Teórico)", "updated_at": "2025-08-22T00:00:24.431267-03:00", "access_period": 360, "created_at": "2024-12-30T00:11:33.088916-03:00", "metadata__konviva_class_id": 1, "tenant_id": "*", "cert": {"exp_interval": 700}, "metadata__unit_price": 119} // User data {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "konviva", "konvivaId": 26943} diff --git a/http-api/app/routes/enrollments/enroll.py b/http-api/app/routes/enrollments/enroll.py index bdf48ec..0af9609 100644 --- a/http-api/app/routes/enrollments/enroll.py +++ b/http-api/app/routes/enrollments/enroll.py @@ -5,7 +5,6 @@ from http import HTTPStatus 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 diff --git a/http-api/app/rules/enrollment.py b/http-api/app/rules/enrollment.py index 5b56eb0..ad17998 100644 --- a/http-api/app/rules/enrollment.py +++ b/http-api/app/rules/enrollment.py @@ -113,48 +113,6 @@ def enroll( 'created_at': now_, }, ) - transact.put( - item={ - 'id': enrollment.id, - # Post-migration: uncomment the following line - # 'sk': LifecycleEvents.REMINDER_NO_ACCESS_3_DAYS, - 'sk': LifecycleEvents.DOES_NOT_ACCESS, - 'name': user.name, - 'email': user.email, - 'course': course.name, - 'created_at': now_, - 'ttl': ttl(days=3, start_dt=now_), - }, - ) - # Enrollment expires by default when the access period ends. - # When the course is finished, it is automatically removed, - # and the `schedules#course_archived` event is created. - transact.put( - item={ - 'id': enrollment.id, - 'sk': LifecycleEvents.EXPIRATION, - # Post-migration: uncomment the following line - # 'sk': LifecycleEvents.COURSE_EXPIRED, - 'name': user.name, - 'email': user.email, - 'course': course.name, - 'created_at': now_, - 'ttl': ttl(start_dt=now_ + timedelta(days=course.access_period)), - }, - ) - transact.put( - item={ - 'id': enrollment.id, - # Post-migration: uncomment the following line - # 'sk': LifecycleEvents.ACCESS_PERIOD_REMINDER_30_DAYS, - 'sk': LifecycleEvents.ACCESS_PERIOD_ENDS, - 'name': user.name, - 'email': user.email, - 'course': course.name, - 'created_at': now_, - 'ttl': ttl(start_dt=now_ + timedelta(days=course.access_period - 30)), - }, - ) for entity in linked_entities: type = entity.type.lower() diff --git a/http-api/compose.yaml b/http-api/compose.yaml index a3823c1..b93ce7d 100644 --- a/http-api/compose.yaml +++ b/http-api/compose.yaml @@ -19,3 +19,17 @@ services: restart: unless-stopped ports: - 7700:7700 + + postgres: + image: postgres + container_name: postgres + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=pgsql@pwd + - PGID=1000 + - PUID=1000 + ports: + - 5432:5432 + volumes: + - ./.postgres_data:/var/lib/postgresql/data diff --git a/http-api/pyproject.toml b/http-api/pyproject.toml index 80e68d2..214d7df 100644 --- a/http-api/pyproject.toml +++ b/http-api/pyproject.toml @@ -10,6 +10,8 @@ dependencies = ["layercake"] dev = [ "boto3-stubs[essential]>=1.38.26", "jsonlines>=4.0.0", + "psycopg2-binary>=2.9.10", + "pycouchdb>=1.16.0", "pytest>=8.3.4", "pytest-cov>=6.0.0", "ruff>=0.9.1", diff --git a/http-api/template.yaml b/http-api/template.yaml index 5f5f479..1e4811a 100644 --- a/http-api/template.yaml +++ b/http-api/template.yaml @@ -40,7 +40,7 @@ Globals: COURSE_TABLE: !Ref CourseTable KONVIVA_API_URL: https://lms.saladeaula.digital KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}" - MEILISEARCH_HOST: https://meili.eduseg.com.br + MEILISEARCH_HOST: https://meili.saladeaula.digital MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}" Resources: diff --git a/http-api/uv.lock b/http-api/uv.lock index bccd5fa..36fa30d 100644 --- a/http-api/uv.lock +++ b/http-api/uv.lock @@ -210,6 +210,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.1" @@ -347,18 +356,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, ] -[[package]] -name = "ecdsa" -version = "0.19.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, -] - [[package]] name = "email-validator" version = "2.2.0" @@ -431,6 +428,8 @@ dependencies = [ dev = [ { name = "boto3-stubs", extra = ["essential"] }, { name = "jsonlines" }, + { name = "psycopg2-binary" }, + { name = "pycouchdb" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, @@ -445,6 +444,8 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] dev = [ { name = "boto3-stubs", extras = ["essential"], specifier = ">=1.38.26" }, { name = "jsonlines", specifier = ">=4.0.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.10" }, + { name = "pycouchdb", specifier = ">=1.16.0" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "ruff", specifier = ">=0.9.1" }, @@ -470,18 +471,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - [[package]] name = "jmespath" version = "1.0.1" @@ -517,7 +506,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.9" +version = "0.9.12" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, @@ -526,7 +515,6 @@ dependencies = [ { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, - { name = "jinja2" }, { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, @@ -534,7 +522,6 @@ dependencies = [ { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, { name = "pyjwt" }, - { name = "python-jose", extra = ["cryptography"] }, { name = "pytz" }, { name = "requests" }, { name = "smart-open", extra = ["s3"] }, @@ -550,7 +537,6 @@ requires-dist = [ { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, { name = "glom", specifier = ">=24.11.0" }, - { name = "jinja2", specifier = ">=3.1.6" }, { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, @@ -558,7 +544,6 @@ requires-dist = [ { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "pyjwt", specifier = ">=2.10.1" }, - { name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" }, { name = "pytz", specifier = ">=2025.1" }, { name = "requests", specifier = ">=2.32.3" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, @@ -577,34 +562,6 @@ dev = [ { name = "ruff", specifier = ">=0.11.1" }, ] -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, -] - [[package]] name = "meilisearch" version = "0.34.0" @@ -740,12 +697,35 @@ wheels = [ ] [[package]] -name = "pyasn1" -version = "0.6.1" +name = "psycopg2-binary" +version = "2.9.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, +] + +[[package]] +name = "pycouchdb" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/b4/4f699a686a2ce14ab31cb17902693f2cf201ba51c3a6fb7aba210725c154/pycouchdb-1.16.0.tar.gz", hash = "sha256:309d71c3ce3f98bbee5731db00f514753438b81e6e7adefbb8c134312200a4f9", size = 11351, upload-time = "2024-05-29T10:00:11.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/63/b4397a7215c089c7951afb258069cc58a06788224f1bb6a0d4f976f2d476/pycouchdb-1.16.0-py3-none-any.whl", hash = "sha256:e26ce58f626fcabbe2f5b15b3ad2b89cdd3f6d666da673632037476d1191ab67", size = 12560, upload-time = "2024-05-29T10:00:09.31Z" }, ] [[package]] @@ -894,25 +874,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, ] -[[package]] -name = "python-jose" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ecdsa" }, - { name = "pyasn1" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" }, -] - -[package.optional-dependencies] -cryptography = [ - { name = "cryptography" }, -] - [[package]] name = "pytz" version = "2025.2" @@ -937,18 +898,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, -] - [[package]] name = "ruff" version = "0.11.2" diff --git a/konviva-events/app/boto3clients.py b/konviva-events/app/boto3clients.py index 05de43d..7d2a8c1 100644 --- a/konviva-events/app/boto3clients.py +++ b/konviva-events/app/boto3clients.py @@ -1,13 +1,19 @@ import os +from typing import TYPE_CHECKING import boto3 +if TYPE_CHECKING: + from mypy_boto3_dynamodb.client import DynamoDBClient +else: + DynamoDBClient = object -def get_dynamodb_client(): + +def get_dynamodb_client() -> DynamoDBClient: if os.getenv('AWS_LAMBDA_FUNCTION_NAME'): return boto3.client('dynamodb') return boto3.client('dynamodb', endpoint_url='http://127.0.0.1:8000') -dynamodb_client = get_dynamodb_client() +dynamodb_client: DynamoDBClient = get_dynamodb_client() diff --git a/konviva-events/app/events/create_user.py b/konviva-events/app/events/create_user.py index 2847aa5..432e93a 100644 --- a/konviva-events/app/events/create_user.py +++ b/konviva-events/app/events/create_user.py @@ -39,7 +39,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: user_id = glom(result, '0.IDUsuario') if not result: - raise UserNotFoundError() + raise UserNotFoundError('User not found') except Exception: raise @@ -47,7 +47,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: transact.update( key=KeyPair(new_image['id'], '0'), update_expr='SET metadata__konviva_user_id = :user_id, \ - updated_at = :updated_at', + updated_at = :updated_at', expr_attr_values={ ':user_id': user_id, ':updated_at': now_, diff --git a/konviva-events/app/events/enroll.py b/konviva-events/app/events/enroll.py index 07670e3..c875c24 100644 --- a/konviva-events/app/events/enroll.py +++ b/konviva-events/app/events/enroll.py @@ -41,6 +41,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ':enrollment_id': enrollment_id, }, ) + # To relate with the enrollment transact.put( item={ 'id': 'konviva', diff --git a/konviva-events/app/konviva.py b/konviva-events/app/konviva.py index 00ae75d..4c6045e 100644 --- a/konviva-events/app/konviva.py +++ b/konviva-events/app/konviva.py @@ -26,7 +26,7 @@ class EmailAlreadyExistsError(KonvivaError): pass -def _raise_konviva_errors_if_any(data: dict = {}) -> None: +def _raise_errors_if_any(data: dict = {}) -> None: """Raises specific exceptions if Konviva API returned errors in the response body""" errors = glom(data, 'errors', default=None) if not errors: @@ -72,7 +72,7 @@ def create_user( ) r.raise_for_status() data = r.json() - _raise_konviva_errors_if_any(data) + _raise_errors_if_any(data) return int(data.get('IDUsuario')) @@ -93,7 +93,7 @@ def update_user(id: str, **kwargs) -> dict: ) r.raise_for_status() data = r.json() - _raise_konviva_errors_if_any(data) + _raise_errors_if_any(data) return data @@ -126,7 +126,7 @@ def _post_enrollment(json: dict) -> dict: ) r.raise_for_status() data = r.json() - _raise_konviva_errors_if_any(data) + _raise_errors_if_any(data) return data diff --git a/konviva-events/pyproject.toml b/konviva-events/pyproject.toml index 7386c57..9a8f08d 100644 --- a/konviva-events/pyproject.toml +++ b/konviva-events/pyproject.toml @@ -8,6 +8,7 @@ dependencies = ["layercake"] [dependency-groups] dev = [ + "boto3-stubs[dynamodb]>=1.40.15", "jsonlines>=4.0.0", "pytest>=8.3.4", "pytest-cov>=6.0.0", diff --git a/konviva-events/template.yaml b/konviva-events/template.yaml index 451b028..f7f9a2d 100644 --- a/konviva-events/template.yaml +++ b/konviva-events/template.yaml @@ -17,7 +17,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:86 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:94 Environment: Variables: TZ: America/Sao_Paulo @@ -36,6 +36,33 @@ Resources: Properties: RetentionInDays: 90 + HttpLog: + Type: AWS::Logs::LogGroup + Properties: + RetentionInDays: 90 + + HttpApi: + Type: AWS::Serverless::HttpApi + Properties: + CorsConfiguration: + AllowOrigins: ["*"] + AllowMethods: [POST, OPTIONS] + AllowHeaders: [Content-Type, X-Requested-With] + + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.lambda_handler + LoggingConfig: + LogGroup: !Ref HttpLog + Events: + Session: + Type: HttpApi + Properties: + Path: / + Method: POST + ApiId: !Ref HttpApi + EventCreateUserFunction: Type: AWS::Serverless::Function Properties: @@ -60,6 +87,29 @@ Resources: metadata__konviva_user_id: - exists: false + EventUpdateUserFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.update_user.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBReadPolicy: + TableName: !Ref UserTable + Events: + DynamoDBEvent: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref UserTable] + detail-type: [MODIFY] + detail: + new_image: + sk: ["0"] + metadata__konviva_user_id: + - exists: true + changes: [name, email, cpf] + EventEnrollFunction: Type: AWS::Serverless::Function Properties: @@ -78,7 +128,7 @@ Resources: detail-type: [INSERT] detail: new_image: - sk: ["konviva"] + sk: [konviva, KONVIVA] enrollment_id: - exists: false @@ -104,26 +154,3 @@ Resources: status: [CANCELED] old_image: status: [PENDING] - - EventUpdateUserFunction: - Type: AWS::Serverless::Function - Properties: - Handler: events.update_user.lambda_handler - LoggingConfig: - LogGroup: !Ref EventLog - Policies: - - DynamoDBReadPolicy: - TableName: !Ref UserTable - Events: - DynamoDBEvent: - Type: EventBridgeRule - Properties: - Pattern: - resources: [!Ref UserTable] - detail-type: [MODIFY] - detail: - new_image: - sk: ["0"] - metadata__konviva_user_id: - - exists: true - changes: [name, email, cpf] diff --git a/konviva-events/tests/conftest.py b/konviva-events/tests/conftest.py index 340de12..1d1ded1 100644 --- a/konviva-events/tests/conftest.py +++ b/konviva-events/tests/conftest.py @@ -1,5 +1,8 @@ +import base64 +import json import os from dataclasses import dataclass +from http import HTTPMethod import jsonlines import pytest @@ -27,11 +30,83 @@ class LambdaContext: aws_request_id: str = '52fdfc07-2182-154f-163f-5f0f9a621d72' +class HttpApiProxy: + def __call__( + self, + raw_path: str, + method: str = HTTPMethod.GET, + body: dict = {}, + *, + headers: dict = {}, + auth_flow_type: str = 'USER_AUTH', + queryStringParameters: dict = {}, + **kwargs, + ) -> dict: + return { + 'version': '2.0', + 'routeKey': '$default', + 'rawPath': raw_path, + 'rawQueryString': 'parameter1=value1¶meter1=value2¶meter2=value', + 'cookies': ['cookie1', 'cookie2'], + 'headers': headers, + 'queryStringParameters': queryStringParameters, + 'requestContext': { + 'accountId': '123456789012', + 'apiId': 'api-id', + 'authorizer': { + 'lambda': { + 'user': { + 'name': 'Sérgio R Siqueira', + 'email': 'sergio@somosbeta.com.br', + 'email_verified': 'true', + 'custom:user_id': '5OxmMjL-ujoR5IMGegQz', + 'sub': 'c4f30dbd-083e-4b84-aa50-c31afe9b9c01', + }, + 'auth_flow_type': auth_flow_type, + }, + 'jwt': { + 'claims': {'claim1': 'value1', 'claim2': 'value2'}, + 'scopes': ['scope1', 'scope2'], + }, + }, + 'domainName': 'id.execute-api.us-east-1.amazonaws.com', + 'domainPrefix': 'id', + 'http': { + 'method': str(method), + 'path': raw_path, + 'protocol': 'HTTP/1.1', + 'sourceIp': '192.168.0.1/32', + 'userAgent': 'agent', + }, + 'requestId': 'id', + 'routeKey': '$default', + 'stage': '$default', + 'time': '12/Mar/2020:19:03:58 +0000', + 'timeEpoch': 1583348638390, + }, + 'body': _base64_dict(body), + 'pathParameters': {'parameter1': 'value1'}, + 'isBase64Encoded': True, + 'stageVariables': {'stageVariable1': 'value1', 'stageVariable2': 'value2'}, + } + + +def _base64_dict(obj: dict = {}) -> str | None: + if not obj: + return None + return base64.b64encode(json.dumps(obj).encode()).decode() + + @pytest.fixture def lambda_context() -> LambdaContext: return LambdaContext() +@pytest.fixture +def http_api_proxy(): + return HttpApiProxy() + + @pytest.fixture def dynamodb_client(): from boto3clients import dynamodb_client as client @@ -57,8 +132,29 @@ def dynamodb_client(): client.delete_table(TableName=PYTEST_TABLE_NAME) +@pytest.fixture +def dynamodb_persistence_layer(dynamodb_client): + from layercake.dynamodb import DynamoDBPersistenceLayer + + return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client) + + @pytest.fixture() -def dynamodb_seeds(dynamodb_client): - with jsonlines.open('tests/seeds.jsonl') as lines: - for line in lines: - dynamodb_client.put_item(TableName=PYTEST_TABLE_NAME, Item=line) +def seeds(dynamodb_client): + from layercake.dynamodb import serialize + + with open('tests/seeds.jsonl', 'rb') as fp: + reader = jsonlines.Reader(fp) + + for line in reader.iter(type=dict, skip_invalid=True): + dynamodb_client.put_item( + TableName=PYTEST_TABLE_NAME, + Item=serialize(line), + ) + + +@pytest.fixture +def app(): + import app + + return app diff --git a/konviva-events/tests/events/test_create_user.py b/konviva-events/tests/events/test_create_user.py index f396f47..7e1373f 100644 --- a/konviva-events/tests/events/test_create_user.py +++ b/konviva-events/tests/events/test_create_user.py @@ -3,7 +3,10 @@ from aws_lambda_powertools.utilities.typing import LambdaContext import events.create_user as app -def test_create_user(dynamodb_client, dynamodb_seeds, lambda_context: LambdaContext): +def test_create_user( + seeds, + lambda_context: LambdaContext, +): event = { 'detail': { 'new_image': { diff --git a/konviva-events/tests/events/test_enroll.py b/konviva-events/tests/events/test_enroll.py index 3ed2b24..e73dccb 100644 --- a/konviva-events/tests/events/test_enroll.py +++ b/konviva-events/tests/events/test_enroll.py @@ -3,7 +3,10 @@ from aws_lambda_powertools.utilities.typing import LambdaContext import events.enroll as app -def test_enroll(dynamodb_client, dynamodb_seeds, lambda_context: LambdaContext): +def test_enroll( + seeds, + lambda_context: LambdaContext, +): event = { 'detail': { 'new_image': { diff --git a/konviva-events/tests/events/test_update_user.py b/konviva-events/tests/events/test_update_user.py index 80bfb04..50b1c05 100644 --- a/konviva-events/tests/events/test_update_user.py +++ b/konviva-events/tests/events/test_update_user.py @@ -14,4 +14,4 @@ def test_update_user(lambda_context: LambdaContext): }, } - assert app.lambda_handler(event, lambda_context) + assert app.lambda_handler(event, lambda_context) # type: ignore diff --git a/konviva-events/tests/seeds.jsonl b/konviva-events/tests/seeds.jsonl index 6f43cf0..e1b68b9 100644 --- a/konviva-events/tests/seeds.jsonl +++ b/konviva-events/tests/seeds.jsonl @@ -1,2 +1,32 @@ -{"id": {"S": "123"}, "sk": {"S": "0"}} -{"id": {"S": "03ad39c6-055e-48d8-acc8-e11385ed8904"}, "sk": {"S": "konviva"}, "user_id": {"N": "275"}, "class_id": {"N": "26943"}} \ No newline at end of file +// Users +{"id": "123", "sk": "0"} + +// Enrollments +{"id": "197991aa-52e2-4e0c-b2a4-a7c53bcfee02", "sk": "0", "progress": 10, "status": "IN_PROGRESS"} + +{"id": "03ad39c6-055e-48d8-acc8-e11385ed8904", "sk": "0"} +{"id": "03ad39c6-055e-48d8-acc8-e11385ed8904", "sk": "konviva", "user_id": 275, "class_id": 26943} + +{"id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a", "sk": "0", "progress": 0, "status": "PENDING"} +{"id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a", "sk": "SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS"} + +{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "0", "progress": 80, "status": "IN_PROGRESS", "user": {"id": "123", "name": "Myles Kennedy"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"} +{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"} +{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-04-06T11:07:32.762178-03:00"} +{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS"} +{"id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8", "sk": "SCHEDULE#SET_AS_EXPIRED"} + +{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "0", "progress": 109, "status": "COMPLETED", "user": {"id": "321", "name": "Chester Bennington"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"} +{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"} +{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-04-06T11:07:32.762178-03:00"} +{"id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae", "sk": "SCHEDULE#SET_AS_ARCHIVED"} + +{"id": "5db53b35-0bae-4907-afda-a213cb5bf651", "sk": "0", "progress": 0, "status": "PENDING", "user": {"id": "1234", "name": "Michael Jackinson"}, "course": {"id": "432", "name": "pytest"}, "created_at": "2022-04-06T11:07:32.762178-03:00"} +{"id": "5db53b35-0bae-4907-afda-a213cb5bf651", "sk": "METADATA#COURSE", "access_period": 360, "cert": {"exp_interval": 365}, "created_at": "2025-04-06T11:07:32.762178-03:00"} + +// To relate with the enrollment +{"id": "konviva", "sk": "123", "enrollment_id": "d9da85f2-e09f-472d-9515-3d91d70f1e8a"} +{"id": "konviva", "sk": "456", "enrollment_id": "197991aa-52e2-4e0c-b2a4-a7c53bcfee02"} +{"id": "konviva", "sk": "567", "enrollment_id": "6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8"} +{"id": "konviva", "sk": "899", "enrollment_id": "cc2c3bce-c34a-4e82-aa6c-1a19e70ec5ae"} +{"id": "konviva", "sk": "221", "enrollment_id": "5db53b35-0bae-4907-afda-a213cb5bf651"} \ No newline at end of file diff --git a/konviva-events/uv.lock b/konviva-events/uv.lock index eddbf0f..04ae4ab 100644 --- a/konviva-events/uv.lock +++ b/konviva-events/uv.lock @@ -29,6 +29,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] +[[package]] +name = "authlib" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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" } +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" }, +] + [[package]] name = "aws-encryption-sdk" version = "4.0.2" @@ -46,15 +58,15 @@ wheels = [ [[package]] name = "aws-lambda-powertools" -version = "3.16.0" +version = "3.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/b9/7e69ec5b47f4c899f0f96f4d1efaa5511ebe753c1cf3d8142ada2ce47381/aws_lambda_powertools-3.16.0.tar.gz", hash = "sha256:df0e5b6e575bf8fd30101d3c39358deb3ccbed195756343e44af34d05345f128", size = 683586, upload-time = "2025-07-02T10:39:41.513Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/db/eb2708f7c27ab02b8d85936ce9308538e1e22c8c8224be5f00da3e6f44f7/aws_lambda_powertools-3.19.0.tar.gz", hash = "sha256:8897ba4be0b3a51f2b8f68946d650f3ef574fa2c40395544de03bd0c61979999", size = 689768, upload-time = "2025-08-12T08:45:46.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/6f/6ffa68d026d26e4be251bff053993e73780e4a2629e755fcbb5432cc77e9/aws_lambda_powertools-3.16.0-py3-none-any.whl", hash = "sha256:b37843d4873b88fedd8eed507fdbf3eb7193e48a6b662b1e7b41e741cdcc765b", size = 824530, upload-time = "2025-07-02T10:39:39.416Z" }, + { url = "https://files.pythonhosted.org/packages/c6/52/5a73194286af329309263e9c4e2a57b8feac63bb6027be8d2d6222cd4da7/aws_lambda_powertools-3.19.0-py3-none-any.whl", hash = "sha256:98f18d35f843cd46b80ccadcf39eefc0c489325bea116383bd93048a5241d9fc", size = 832645, upload-time = "2025-08-12T08:45:44.982Z" }, ] [package.optional-dependencies] @@ -103,6 +115,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/5c/93292e4d8c809950c13950b3168e0eabdac828629c21047959251ad3f28c/boto3-1.39.4-py3-none-any.whl", hash = "sha256:f8e9534b429121aa5c5b7c685c6a94dd33edf14f87926e9a182d5b50220ba284", size = 139908, upload-time = "2025-07-09T19:22:59.808Z" }, ] +[[package]] +name = "boto3-stubs" +version = "1.40.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore-stubs" }, + { name = "types-s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/54/c8a0d43c5d17e20433b23ee78cc8348b0cba5a5255d6c2f66aafa86c64ad/boto3_stubs-1.40.15.tar.gz", hash = "sha256:47370ffdfd9f1899900bba554f4ae1846423c459beaccf11e2eae46896af5119", size = 101393, upload-time = "2025-08-21T19:48:27.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/40/fe3cb27e3eee35815902a37d26b7c0af308fe2b08cd0671fb0a8475dfa12/boto3_stubs-1.40.15-py3-none-any.whl", hash = "sha256:95b6a828b758ed56d90ea2530a6794506ca403cfbef3bd2584a2e7c43e3f6607", size = 70011, upload-time = "2025-08-21T19:48:20.458Z" }, +] + +[package.optional-dependencies] +dynamodb = [ + { name = "mypy-boto3-dynamodb" }, +] + [[package]] name = "botocore" version = "1.39.4" @@ -118,40 +148,15 @@ wheels = [ ] [[package]] -name = "brotli" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, - { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, - { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, -] - -[[package]] -name = "brotlicffi" -version = "1.1.0.0" +name = "botocore-stubs" +version = "1.38.46" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi" }, + { name = "types-awscrt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192, upload-time = "2023-09-14T14:22:40.707Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/45/27cabc7c3022dcb12de5098cc646b374065f5e72fae13600ff1756f365ee/botocore_stubs-1.38.46.tar.gz", hash = "sha256:a04e69766ab8bae338911c1897492f88d05cd489cd75f06e6eb4f135f9da8c7b", size = 42299, upload-time = "2025-06-29T22:58:24.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786, upload-time = "2023-09-14T14:21:57.72Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165, upload-time = "2023-09-14T14:21:59.613Z" }, - { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895, upload-time = "2023-09-14T14:22:01.22Z" }, - { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834, upload-time = "2023-09-14T14:22:03.571Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731, upload-time = "2023-09-14T14:22:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783, upload-time = "2023-09-14T14:22:07.096Z" }, + { url = "https://files.pythonhosted.org/packages/cc/84/06490071e26bab22ac79a684e98445df118adcf80c58c33ba5af184030f2/botocore_stubs-1.38.46-py3-none-any.whl", hash = "sha256:cc21d9a7dd994bdd90872db4664d817c4719b51cda8004fd507a4bf65b085a75", size = 66083, upload-time = "2025-06-29T22:58:22.234Z" }, ] [[package]] @@ -320,19 +325,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, ] -[[package]] -name = "cssselect2" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tinycss2" }, - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716, upload-time = "2025-03-05T14:46:07.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454, upload-time = "2025-03-05T14:46:06.463Z" }, -] - [[package]] name = "dictdiffer" version = "0.9.0" @@ -385,30 +377,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924, upload-time = "2024-12-02T10:55:07.599Z" }, ] -[[package]] -name = "fonttools" -version = "4.58.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/97/5735503e58d3816b0989955ef9b2df07e4c99b246469bd8b3823a14095da/fonttools-4.58.5.tar.gz", hash = "sha256:b2a35b0a19f1837284b3a23dd64fd7761b8911d50911ecd2bdbaf5b2d1b5df9c", size = 3526243, upload-time = "2025-07-03T14:04:47.736Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/ee/764dd8b99891f815241f449345863cfed9e546923d9cef463f37fd1d7168/fonttools-4.58.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f4b6f1360da13cecc88c0d60716145b31e1015fbe6a59e32f73a4404e2ea92cf", size = 2745867, upload-time = "2025-07-03T14:04:10.586Z" }, - { url = "https://files.pythonhosted.org/packages/e2/23/8fef484c02fef55e226dfeac4339a015c5480b6a496064058491759ac71e/fonttools-4.58.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a036822e915692aa2c03e2decc60f49a8190f8111b639c947a4f4e5774d0d7a", size = 2317933, upload-time = "2025-07-03T14:04:12.335Z" }, - { url = "https://files.pythonhosted.org/packages/ab/47/f92b135864fa777e11ad68420bf89446c91a572fe2782745586f8e6aac0c/fonttools-4.58.5-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d7709fcf4577b0f294ee6327088884ca95046e1eccde87c53bbba4d5008541", size = 4877844, upload-time = "2025-07-03T14:04:14.58Z" }, - { url = "https://files.pythonhosted.org/packages/3e/65/6c1a83511d8ac32411930495645edb3f8dfabebcb78f08cf6009ba2585ec/fonttools-4.58.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9b5099ca99b79d6d67162778b1b1616fc0e1de02c1a178248a0da8d78a33852", size = 4940106, upload-time = "2025-07-03T14:04:16.563Z" }, - { url = "https://files.pythonhosted.org/packages/fa/90/df8eb77d6cf266cbbba01866a1349a3e9121e0a63002cf8d6754e994f755/fonttools-4.58.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f2c05a8d82a4d15aebfdb3506e90793aea16e0302cec385134dd960647a36c0", size = 4879458, upload-time = "2025-07-03T14:04:19.584Z" }, - { url = "https://files.pythonhosted.org/packages/26/b1/e32f8de51b7afcfea6ad62780da2fa73212c43a32cd8cafcc852189d7949/fonttools-4.58.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79f0c4b1cc63839b61deeac646d8dba46f8ed40332c2ac1b9997281462c2e4ba", size = 5021917, upload-time = "2025-07-03T14:04:21.736Z" }, - { url = "https://files.pythonhosted.org/packages/89/72/578aa7fe32918dd763c62f447aaed672d665ee10e3eeb1725f4d6493fe96/fonttools-4.58.5-cp313-cp313-win32.whl", hash = "sha256:a1a9a2c462760976882131cbab7d63407813413a2d32cd699e86a1ff22bf7aa5", size = 2186827, upload-time = "2025-07-03T14:04:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/71/a3/21e921b16cb9c029d3308e0cb79c9a937e9ff1fc1ee28c2419f0957b9e7c/fonttools-4.58.5-cp313-cp313-win_amd64.whl", hash = "sha256:bca61b14031a4b7dc87e14bf6ca34c275f8e4b9f7a37bc2fe746b532a924cf30", size = 2235706, upload-time = "2025-07-03T14:04:26.082Z" }, - { url = "https://files.pythonhosted.org/packages/d7/d4/1d85a1996b6188cd2713230e002d79a6f3a289bb17cef600cba385848b72/fonttools-4.58.5-py3-none-any.whl", hash = "sha256:e48a487ed24d9b611c5c4b25db1e50e69e9854ca2670e39a3486ffcd98863ec4", size = 1115318, upload-time = "2025-07-03T14:04:45.378Z" }, -] - -[package.optional-dependencies] -woff = [ - { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, - { name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" }, - { name = "zopfli" }, -] - [[package]] name = "ftfy" version = "6.3.1" @@ -453,18 +421,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - [[package]] name = "jmespath" version = "1.0.1" @@ -508,6 +464,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "boto3-stubs", extra = ["dynamodb"] }, { name = "jsonlines" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -519,6 +476,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] [package.metadata.requires-dev] dev = [ + { name = "boto3-stubs", extras = ["dynamodb"], specifier = ">=1.40.15" }, { name = "jsonlines", specifier = ">=4.0.0" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-cov", specifier = ">=6.0.0" }, @@ -527,49 +485,49 @@ dev = [ [[package]] name = "layercake" -version = "0.7.2" +version = "0.9.12" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, + { name = "authlib" }, { name = "aws-lambda-powertools", extra = ["all"] }, { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, - { name = "jinja2" }, { name = "meilisearch" }, { name = "orjson" }, + { name = "passlib" }, { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, + { name = "pyjwt" }, { name = "pytz" }, - { name = "qrcode" }, { name = "requests" }, { name = "smart-open", extra = ["s3"] }, { name = "sqlite-utils" }, { name = "unidecode" }, - { name = "weasyprint" }, ] [package.metadata] requires-dist = [ { name = "arnparse", specifier = ">=0.0.2" }, - { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.8.0" }, + { name = "authlib", specifier = ">=1.6.1" }, + { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" }, { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, { name = "glom", specifier = ">=24.11.0" }, - { name = "jinja2", specifier = ">=3.1.6" }, { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, + { name = "passlib", specifier = ">=1.7.4" }, { name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, + { name = "pyjwt", specifier = ">=2.10.1" }, { name = "pytz", specifier = ">=2025.1" }, - { name = "qrcode", specifier = ">=8.2" }, { name = "requests", specifier = ">=2.32.3" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, { name = "sqlite-utils", specifier = ">=3.38" }, { name = "unidecode", specifier = ">=1.4.0" }, - { name = "weasyprint", specifier = ">=65.0" }, ] [package.metadata.requires-dev] @@ -577,39 +535,12 @@ dev = [ { name = "boto3", specifier = ">=1.37.16" }, { name = "boto3-stubs", extras = ["essential"], specifier = ">=1.37.33" }, { name = "jsonlines", specifier = ">=4.0.0" }, + { name = "moto", extras = ["all"], specifier = ">=5.1.9" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "ruff", specifier = ">=0.11.1" }, ] -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, -] - [[package]] name = "meilisearch" version = "0.36.0" @@ -623,6 +554,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/fb/cbc1247429269460e5eb762a798beb702953673d88ce87a26a21263a44d8/meilisearch-0.36.0-py3-none-any.whl", hash = "sha256:f3f0882da7531c038fc6698f18fa492c7d46f8e238600a5af9eb627c7ff21d9e", size = 27666, upload-time = "2025-06-20T02:14:09.268Z" }, ] +[[package]] +name = "mypy-boto3-dynamodb" +version = "1.40.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/85/f5d4261c084cac14e4f19bb074f9292f68e18493174289fb21e07339f25c/mypy_boto3_dynamodb-1.40.14.tar.gz", hash = "sha256:7ec8eb714ac080e7d5572ec8c556953930aba5d2fbcc058aa3cbb87ccce4ac79", size = 47978, upload-time = "2025-08-20T19:27:30.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/b3/6f2e15a44e66a8cc98fd1032f3aba770f946ba361782a0a979a115fdf6e2/mypy_boto3_dynamodb-1.40.14-py3-none-any.whl", hash = "sha256:302cc169dde3b87a41924855dcfbae173247e18833dee80919f7cc690189f376", size = 57017, upload-time = "2025-08-20T19:27:20.701Z" }, +] + [[package]] name = "orjson" version = "3.10.18" @@ -656,58 +596,12 @@ wheels = [ ] [[package]] -name = "pillow" -version = "11.3.0" +name = "passlib" +version = "1.7.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, - { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, - { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" }, ] [[package]] @@ -821,15 +715,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] -[[package]] -name = "pydyf" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/c2/97fc6ce4ce0045080dc99446def812081b57750ed8aa67bfdfafa4561fe5/pydyf-0.11.0.tar.gz", hash = "sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64", size = 17769, upload-time = "2024-07-12T12:26:51.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/ac/d5db977deaf28c6ecbc61bbca269eb3e8f0b3a1f55c8549e5333e606e005/pydyf-0.11.0-py3-none-any.whl", hash = "sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3", size = 8104, upload-time = "2024-07-12T12:26:49.896Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -840,12 +725,12 @@ wheels = [ ] [[package]] -name = "pyphen" -version = "0.17.2" +name = "pyjwt" +version = "2.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/56/e4d7e1bd70d997713649c5ce530b2d15a5fc2245a74ca820fc2d51d89d4d/pyphen-0.17.2.tar.gz", hash = "sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3", size = 2079470, upload-time = "2025-01-20T13:18:36.296Z" } +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/7b/1f/c2142d2edf833a90728e5cdeb10bdbdc094dde8dbac078cee0cf33f5e11b/pyphen-0.17.2-py3-none-any.whl", hash = "sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd", size = 2079358, upload-time = "2025-01-20T13:18:29.629Z" }, + { 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]] @@ -908,18 +793,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] -[[package]] -name = "qrcode" -version = "8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, -] - [[package]] name = "requests" version = "2.32.4" @@ -1034,27 +907,21 @@ wheels = [ ] [[package]] -name = "tinycss2" -version = "1.4.0" +name = "types-awscrt" +version = "0.27.6" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/ce/5d84526a39f44c420ce61b16654193f8437d74b54f21597ea2ac65d89954/types_awscrt-0.27.6.tar.gz", hash = "sha256:9d3f1865a93b8b2c32f137514ac88cb048b5bc438739945ba19d972698995bfb", size = 16937, upload-time = "2025-08-13T01:54:54.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/ac/af/e3d20e3e81d235b3964846adf46a334645a8a9b25a0d3d472743eb079552/types_awscrt-0.27.6-py3-none-any.whl", hash = "sha256:18aced46da00a57f02eb97637a32e5894dc5aa3dc6a905ba3e5ed85b9f3c526b", size = 39626, upload-time = "2025-08-13T01:54:53.454Z" }, ] [[package]] -name = "tinyhtml5" -version = "2.0.0" +name = "types-s3transfer" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/03/6111ed99e9bf7dfa1c30baeef0e0fb7e0bd387bd07f8e5b270776fe1de3f/tinyhtml5-2.0.0.tar.gz", hash = "sha256:086f998833da24c300c414d9fe81d9b368fd04cb9d2596a008421cbc705fcfcc", size = 179507, upload-time = "2024-10-29T15:37:14.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/c1/45038f259d6741c252801044e184fec4dbaeff939a58f6160d7c32bf4975/types_s3transfer-0.13.0.tar.gz", hash = "sha256:203dadcb9865c2f68fb44bc0440e1dc05b79197ba4a641c0976c26c9af75ef52", size = 14175, upload-time = "2025-05-28T02:16:07.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/de/27c57899297163a4a84104d5cec0af3b1ac5faf62f44667e506373c6b8ce/tinyhtml5-2.0.0-py3-none-any.whl", hash = "sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e", size = 39793, upload-time = "2024-10-29T15:37:11.743Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5d/6bbe4bf6a79fb727945291aef88b5ecbdba857a603f1bbcf1a6be0d3f442/types_s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:79c8375cbf48a64bff7654c02df1ec4b20d74f8c5672fc13e382f593ca5565b3", size = 19588, upload-time = "2025-05-28T02:16:06.709Z" }, ] [[package]] @@ -1105,34 +972,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] -[[package]] -name = "weasyprint" -version = "65.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, - { name = "cssselect2" }, - { name = "fonttools", extra = ["woff"] }, - { name = "pillow" }, - { name = "pydyf" }, - { name = "pyphen" }, - { name = "tinycss2" }, - { name = "tinyhtml5" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/76/7f865f0019120be20276813097b5729b8487b93dd4aff339aa77ed8c7ad2/weasyprint-65.1.tar.gz", hash = "sha256:120281bdbd42ffaa7d7e5cedbe3182a2cef36ea5ad97fe9f357e43be6a1e58ea", size = 499028, upload-time = "2025-04-14T12:15:02.654Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/9a/14f4e5fd4bba988d3684602b72f04c0b299c0f368d26c11a79ceab97aa68/weasyprint-65.1-py3-none-any.whl", hash = "sha256:9baa54282dc86929f6b877034d06b0416e2a7cacb1af3f73d80960592fd0af89", size = 298040, upload-time = "2025-04-14T12:15:00.695Z" }, -] - -[[package]] -name = "webencodings" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, -] - [[package]] name = "wrapt" version = "1.17.2" @@ -1163,21 +1002,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" }, { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, ] - -[[package]] -name = "zopfli" -version = "0.2.3.post1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/7c/a8f6696e694709e2abcbccd27d05ef761e9b6efae217e11d977471555b62/zopfli-0.2.3.post1.tar.gz", hash = "sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99", size = 175629, upload-time = "2024-10-18T15:42:05.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/24/0e552e2efce9a20625b56e9609d1e33c2966be33fc008681121ec267daec/zopfli-0.2.3.post1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051", size = 295485, upload-time = "2024-10-18T15:41:12.57Z" }, - { url = "https://files.pythonhosted.org/packages/08/83/b2564369fb98797a617fe2796097b1d719a4937234375757ad2a3febc04b/zopfli-0.2.3.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f", size = 163000, upload-time = "2024-10-18T15:41:13.743Z" }, - { url = "https://files.pythonhosted.org/packages/3c/55/81d419739c2aab35e19b58bce5498dcb58e6446e5eb69f2d3c748b1c9151/zopfli-0.2.3.post1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a", size = 823699, upload-time = "2024-10-18T15:41:14.874Z" }, - { url = "https://files.pythonhosted.org/packages/9e/91/89f07c8ea3c9bc64099b3461627b07a8384302235ee0f357eaa86f98f509/zopfli-0.2.3.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f", size = 826612, upload-time = "2024-10-18T15:41:16.069Z" }, - { url = "https://files.pythonhosted.org/packages/41/31/46670fc0c7805d42bc89702440fa9b73491d68abbc39e28d687180755178/zopfli-0.2.3.post1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c", size = 851148, upload-time = "2024-10-18T15:41:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/22/00/71ad39277bbb88f9fd20fb786bd3ff2ea4025c53b31652a0da796fb546cd/zopfli-0.2.3.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6", size = 1754215, upload-time = "2024-10-18T15:41:18.661Z" }, - { url = "https://files.pythonhosted.org/packages/d0/4e/e542c508d20c3dfbef1b90fcf726f824f505e725747f777b0b7b7d1deb95/zopfli-0.2.3.post1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54", size = 1905988, upload-time = "2024-10-18T15:41:19.933Z" }, - { url = "https://files.pythonhosted.org/packages/ba/a5/817ac1ecc888723e91dc172e8c6eeab9f48a1e52285803b965084e11bbd5/zopfli-0.2.3.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1", size = 1835907, upload-time = "2024-10-18T15:41:21.582Z" }, - { url = "https://files.pythonhosted.org/packages/cd/35/2525f90c972d8aafc39784a8c00244eeee8e8221b26cbc576748ee9dc1cd/zopfli-0.2.3.post1-cp313-cp313-win32.whl", hash = "sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775", size = 82742, upload-time = "2024-10-18T15:41:23.362Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c6/49b27570923956d52d37363e8f5df3a31a61bd7719bb8718527a9df3ae5f/zopfli-0.2.3.post1-cp313-cp313-win_amd64.whl", hash = "sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de", size = 99408, upload-time = "2024-10-18T15:41:24.377Z" }, -] diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 804bff6..52b30be 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.9.12" +version = "0.9.14" description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." readme = "README.md" authors = [ @@ -26,6 +26,7 @@ dependencies = [ "authlib>=1.6.1", "passlib>=1.7.4", "pyjwt>=2.10.1", + "psycopg[binary]>=3.2.9", ] [dependency-groups] diff --git a/layercake/uv.lock b/layercake/uv.lock index eb87007..c471302 100644 --- a/layercake/uv.lock +++ b/layercake/uv.lock @@ -675,7 +675,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.11" +version = "0.9.13" source = { editable = "." } dependencies = [ { name = "arnparse" }, @@ -687,6 +687,7 @@ dependencies = [ { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, + { name = "psycopg", extra = ["binary"] }, { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, @@ -720,6 +721,7 @@ requires-dist = [ { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" }, { name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, @@ -1059,6 +1061,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, ] +[[package]] +name = "psycopg" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" }, + { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" }, + { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" }, + { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, +] + [[package]] name = "py-partiql-parser" version = "0.6.1" @@ -1643,6 +1692,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + [[package]] name = "unidecode" version = "1.4.0" diff --git a/streams-events/app/config.py b/streams-events/app/config.py index 199a75a..240c5a2 100644 --- a/streams-events/app/config.py +++ b/streams-events/app/config.py @@ -2,3 +2,9 @@ import os MEILISEARCH_HOST: str = os.getenv('MEILISEARCH_HOST') # type: ignore MEILISEARCH_API_KEY: str = os.getenv('MEILISEARCH_API_KEY') # type: ignore + +POSTGRES_DB: str = os.getenv('POSTGRES_DB') # type: ignore +POSTGRES_HOST: str = os.getenv('POSTGRES_HOST') # type: ignore +POSTGRES_PORT: str = os.getenv('POSTGRES_PORT') # type: ignore +POSTGRES_USER: str = os.getenv('POSTGRES_USER') # type: ignore +POSTGRES_PASSWORD: str = os.getenv('POSTGRES_PASSWORD') # type: ignore diff --git a/streams-events/app/events/docs_into_eventbus.py b/streams-events/app/events/docs_into_eventbus.py index c176fb1..c3e77fc 100644 --- a/streams-events/app/events/docs_into_eventbus.py +++ b/streams-events/app/events/docs_into_eventbus.py @@ -16,6 +16,7 @@ from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import ) from aws_lambda_powertools.utilities.typing import LambdaContext from layercake.dateutils import now, ttl + from utils import diff, table_from_arn if TYPE_CHECKING: diff --git a/streams-events/app/events/index_docs_into_meili.py b/streams-events/app/events/index_docs_into_meili.py index c136bbf..3d72f45 100644 --- a/streams-events/app/events/index_docs_into_meili.py +++ b/streams-events/app/events/index_docs_into_meili.py @@ -4,9 +4,10 @@ from aws_lambda_powertools.utilities.data_classes import ( event_source, ) from aws_lambda_powertools.utilities.typing import LambdaContext +from meilisearch import Client as Meilisearch + from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST from meili import Op -from meilisearch import Client as Meilisearch from utils import table_from_arn logger = Logger(__name__) diff --git a/streams-events/app/events/replicate_into_meili.py b/streams-events/app/events/replicate_into_meili.py deleted file mode 100644 index 239d888..0000000 --- a/streams-events/app/events/replicate_into_meili.py +++ /dev/null @@ -1,33 +0,0 @@ -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.utilities.data_classes import ( - DynamoDBStreamEvent, - event_source, -) -from aws_lambda_powertools.utilities.typing import LambdaContext -from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST -from layercake.strutils import md5_hash -from meili import Op -from meilisearch import Client as Meilisearch -from utils import table_from_arn - -logger = Logger(__name__) -tracer = Tracer() -meili_client = Meilisearch(MEILISEARCH_HOST, MEILISEARCH_API_KEY) - - -@event_source(data_class=DynamoDBStreamEvent) -@logger.inject_lambda_context -@tracer.capture_lambda_handler -def lambda_handler(event: DynamoDBStreamEvent, context: LambdaContext): - with Op(meili_client) as op: - for record in event.records: - new_image = record.dynamodb.new_image # type: ignore - index = table_from_arn(record.event_source_arn) # type: ignore - keys = record.dynamodb.keys # type: ignore - _id = md5_hash(str(keys)) - - op.append( - f'_{index}_replication', - op=record.event_name, # type: ignore - data=({'_id': _id} | new_image) or _id, - ) diff --git a/streams-events/pyproject.toml b/streams-events/pyproject.toml index a3458df..e9affc7 100644 --- a/streams-events/pyproject.toml +++ b/streams-events/pyproject.toml @@ -21,8 +21,15 @@ dev = [ pythonpath = ["app/"] addopts = "--cov --cov-report html -v" +[tool.ruff] +target-version = "py311" +src = ["app"] + [tool.ruff.format] quote-style = "single" +[tool.ruff.lint] +select = ["E", "F", "I"] + [tool.uv.sources] layercake = { path = "../layercake" } diff --git a/streams-events/template.yaml b/streams-events/template.yaml index d14e769..cb42ad1 100644 --- a/streams-events/template.yaml +++ b/streams-events/template.yaml @@ -8,15 +8,20 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:94 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:96 Environment: Variables: - LOG_LEVEL: INFO + LOG_LEVEL: DEBUG TZ: America/Sao_Paulo POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 POWERTOOLS_LOGGER_LOG_EVENT: true - MEILISEARCH_HOST: https://meili.eduseg.com.br + MEILISEARCH_HOST: https://meili.saladeaula.digital MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}" + POSTGRES_DB: saladeaula.digital + POSTGRES_HOST: sp-node01.saladeaula.digital + POSTGRES_PORT: 5432 + POSTGRES_USER: "{{resolve:ssm:/saladeaula/postgres_user}}" + POSTGRES_PASSWORD: "{{resolve:ssm:/saladeaula/postgres_password}}" Resources: MeilisearchLog: @@ -29,6 +34,11 @@ Resources: Properties: RetentionInDays: 90 + EventPostgresLog: + Type: AWS::Logs::LogGroup + Properties: + RetentionInDays: 90 + EventIndexDocsIntoMeiliFunction: Type: AWS::Serverless::Function Properties: @@ -46,7 +56,6 @@ Resources: FilterCriteria: Filters: - Pattern: '{ "dynamodb" : { "Keys" : { "sk" : { "S" : [ "0" ] } } } }' - Users: Type: DynamoDB Properties: @@ -57,7 +66,6 @@ Resources: FilterCriteria: Filters: - Pattern: '{ "dynamodb" : { "Keys" : { "sk" : { "S" : [ "0" ] } } } }' - Orders: Type: DynamoDB Properties: @@ -68,7 +76,6 @@ Resources: FilterCriteria: Filters: - Pattern: '{ "dynamodb" : { "Keys" : { "sk" : { "S" : [ "0" ] } } } }' - Courses: Type: DynamoDB Properties: @@ -97,7 +104,7 @@ Resources: StartingPosition: LATEST MaximumRetryAttempts: 5 BatchSize: 25 - Enrolllments: + Enrollments: Type: DynamoDB Properties: Stream: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/betaeducacao-prod-enrollments/stream/2023-08-22T22:56:55.612 @@ -119,15 +126,12 @@ Resources: MaximumRetryAttempts: 5 BatchSize: 25 - EventReplicateIntoMeiliFunction: + EventReplicateIntoPostgresFunction: Type: AWS::Serverless::Function Properties: - Handler: events.replicate_into_meili.lambda_handler + Handler: events.replicate_into_postgres.lambda_handler LoggingConfig: - LogGroup: !Ref EventBusLog - Policies: - - EventBridgePutEventsPolicy: - EventBusName: default + LogGroup: !Ref EventPostgresLog Events: Users: Type: DynamoDB @@ -136,7 +140,7 @@ Resources: StartingPosition: LATEST MaximumRetryAttempts: 5 BatchSize: 25 - Enrolllments: + Enrollments: Type: DynamoDB Properties: Stream: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/betaeducacao-prod-enrollments/stream/2023-08-22T22:56:55.612 diff --git a/streams-events/tests/conftest.py b/streams-events/tests/conftest.py index 3b06bae..b7b40b2 100644 --- a/streams-events/tests/conftest.py +++ b/streams-events/tests/conftest.py @@ -9,6 +9,11 @@ import pytest def pytest_configure(): os.environ['TZ'] = 'America/Sao_Paulo' os.environ['MEILISEARCH_HOST'] = 'http://127.0.0.1:7700' + os.environ['POSTGRES_DB'] = 'pytest' + os.environ['POSTGRES_HOST'] = 'localhost' + os.environ['POSTGRES_PORT'] = '5432' + os.environ['POSTGRES_USER'] = 'postgres' + os.environ['POSTGRES_PASSWORD'] = 'pgsql@pwd' def load_jsonfile(path: str) -> dict: diff --git a/streams-events/tests/test_replicate_into_meili.py b/streams-events/tests/test_replicate_into_meili.py deleted file mode 100644 index 9aede9f..0000000 --- a/streams-events/tests/test_replicate_into_meili.py +++ /dev/null @@ -1,5 +0,0 @@ -import app.events.replicate_into_meili as app - - -def test_record_handler(dynamodb_stream_event, lambda_context): - app.lambda_handler(dynamodb_stream_event, lambda_context) diff --git a/streams-events/uv.lock b/streams-events/uv.lock index 2d1278c..76b1b72 100644 --- a/streams-events/uv.lock +++ b/streams-events/uv.lock @@ -484,7 +484,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.12" +version = "0.9.14" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, @@ -496,6 +496,7 @@ dependencies = [ { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, + { name = "psycopg", extra = ["binary"] }, { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, @@ -518,6 +519,7 @@ requires-dist = [ { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" }, { name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, @@ -634,6 +636,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, ] +[[package]] +name = "psycopg" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" }, + { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" }, + { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" }, + { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, +] + [[package]] name = "pycouchdb" version = "1.16.0" @@ -1003,6 +1052,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, ] +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + [[package]] name = "unidecode" version = "1.4.0"