From 5ae2128dee5e58ed091431a9ac3a0b10a3d6c453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 3 Oct 2025 19:31:41 -0300 Subject: [PATCH] wip --- api.saladeaula.digital/app/app.py | 43 +- api.saladeaula.digital/app/boto3clients.py | 7 +- api.saladeaula.digital/app/config.py | 1 + api.saladeaula.digital/app/json_encoder.py | 11 + .../app/routes/courses/__init__.py | 71 +++ .../app/routes/enrollments/__init__.py | 19 + .../app/routes/orders/__init__.py | 0 .../app/routes/users/__init__.py | 0 .../app/routes/users/emails.py | 0 api.saladeaula.digital/pyproject.toml | 6 +- api.saladeaula.digital/pyrightconfig.json | 3 + api.saladeaula.digital/template.yaml | 8 +- api.saladeaula.digital/tests/conftest.py | 154 ++++++ .../tests/routes/__init__.py | 0 .../tests/routes/test_courses.py | 51 ++ api.saladeaula.digital/tests/sample.html | 466 ++++++++++++++++++ api.saladeaula.digital/tests/seeds.jsonl | 1 + api.saladeaula.digital/uv.lock | 111 ++--- id.saladeaula.digital/app/boto3clients.py | 18 +- .../apigateway_oauth2/authorization_server.py | 1 - .../client/app/entry.server.tsx | 34 +- .../client/app/routes/index.tsx | 2 +- id.saladeaula.digital/pyproject.toml | 5 +- .../tests/routes/test_revoke.py | 3 +- .../tests/routes/test_token.py | 11 +- id.saladeaula.digital/tests/seeds.jsonl | 2 +- id.saladeaula.digital/uv.lock | 119 +++++ layercake/pyproject.toml | 2 +- layercake/uv.lock | 15 +- 29 files changed, 996 insertions(+), 168 deletions(-) create mode 100644 api.saladeaula.digital/app/json_encoder.py create mode 100644 api.saladeaula.digital/app/routes/courses/__init__.py create mode 100644 api.saladeaula.digital/app/routes/enrollments/__init__.py create mode 100644 api.saladeaula.digital/app/routes/orders/__init__.py create mode 100644 api.saladeaula.digital/app/routes/users/__init__.py create mode 100644 api.saladeaula.digital/app/routes/users/emails.py create mode 100644 api.saladeaula.digital/pyrightconfig.json create mode 100644 api.saladeaula.digital/tests/conftest.py create mode 100644 api.saladeaula.digital/tests/routes/__init__.py create mode 100644 api.saladeaula.digital/tests/routes/test_courses.py create mode 100644 api.saladeaula.digital/tests/sample.html create mode 100644 api.saladeaula.digital/tests/seeds.jsonl diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index 23db3b4..e5b715e 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -1,6 +1,5 @@ import json import os -from datetime import date from functools import partial from typing import Any @@ -9,26 +8,13 @@ from aws_lambda_powertools.event_handler.api_gateway import ( APIGatewayHttpResolver, CORSConfig, ) -from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError +from aws_lambda_powertools.event_handler.exceptions import ServiceError from aws_lambda_powertools.logging import correlation_paths -from aws_lambda_powertools.shared.json_encoder import Encoder from aws_lambda_powertools.utilities.typing import LambdaContext -from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from api_gateway import JSONResponse -from boto3clients import dynamodb_client -from config import COURSE_TABLE - -dyn = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) - - -class JSONEncoder(Encoder): - def default(self, obj): - if isinstance(obj, date): - return obj.isoformat() - - return super().default(obj) - +from json_encoder import JSONEncoder +from routes import courses, enrollments tracer = Tracer() logger = Logger(__name__) @@ -44,27 +30,8 @@ app = APIGatewayHttpResolver( debug='AWS_SAM_LOCAL' in os.environ, serializer=partial(json.dumps, separators=(',', ':'), cls=JSONEncoder), ) - - -@app.get('/users/') -@tracer.capture_method -def get_user(user_id: str): - return {'id': user_id} - - -@app.get('/users//emails') -@tracer.capture_method -def get_emails(user_id: str): - return [{'email': 'sergio@somosbeta.com.br'}] - - -@app.get('/courses/') -@tracer.capture_method -def get_course(course_id: str): - return dyn.collection.get_item( - KeyPair(course_id, '0'), - exc_cls=NotFoundError, - ) +app.include_router(courses.router, prefix='/courses') +app.include_router(enrollments.router, prefix='/enrollments') @app.exception_handler(ServiceError) diff --git a/api.saladeaula.digital/app/boto3clients.py b/api.saladeaula.digital/app/boto3clients.py index 00b88cd..6e333d2 100644 --- a/api.saladeaula.digital/app/boto3clients.py +++ b/api.saladeaula.digital/app/boto3clients.py @@ -8,14 +8,13 @@ if TYPE_CHECKING: else: DynamoDBClient = object -AWS_SAM_LOCAL = os.getenv('AWS_SAM_LOCAL') - def get_dynamodb_client() -> DynamoDBClient: - if not AWS_SAM_LOCAL: + if os.getenv('AWS_LAMBDA_FUNCTION_NAME'): return boto3.client('dynamodb') - host = 'host.docker.internal' if AWS_SAM_LOCAL else '127.0.0.1' + # Use the Docker network address when running in a container + host = 'host.docker.internal' if os.getenv('AWS_SAM_LOCAL') else '127.0.0.1' return boto3.client('dynamodb', endpoint_url=f'http://{host}:8000') diff --git a/api.saladeaula.digital/app/config.py b/api.saladeaula.digital/app/config.py index 7662445..96e8b69 100644 --- a/api.saladeaula.digital/app/config.py +++ b/api.saladeaula.digital/app/config.py @@ -1,4 +1,5 @@ import os USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore +ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore diff --git a/api.saladeaula.digital/app/json_encoder.py b/api.saladeaula.digital/app/json_encoder.py new file mode 100644 index 0000000..b2dd5bc --- /dev/null +++ b/api.saladeaula.digital/app/json_encoder.py @@ -0,0 +1,11 @@ +from datetime import date + +from aws_lambda_powertools.shared.json_encoder import Encoder + + +class JSONEncoder(Encoder): + def default(self, obj): + if isinstance(obj, date): + return obj.isoformat() + + return super().default(obj) diff --git a/api.saladeaula.digital/app/routes/courses/__init__.py b/api.saladeaula.digital/app/routes/courses/__init__.py new file mode 100644 index 0000000..9c6afc4 --- /dev/null +++ b/api.saladeaula.digital/app/routes/courses/__init__.py @@ -0,0 +1,71 @@ +from io import BytesIO + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.exceptions import ( + BadRequestError, + NotFoundError, +) +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair +from pydantic import UUID4, BaseModel +from python_multipart import parse_form + +from boto3clients import dynamodb_client +from config import COURSE_TABLE + +logger = Logger(__name__) +router = Router() +dyn = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) + + +@router.get('/') +def get_course(course_id: str): + return dyn.collection.get_item( + KeyPair(course_id, '0'), + exc_cls=NotFoundError, + ) + + +class Cert(BaseModel): + exp_interval: int + rawfile: bytes + + +class FormData(BaseModel): + id: UUID4 + name: str + access_period: int + cert: Cert | None = None + + +@router.post('/') +def edit_course(course_id: str): + event = router.current_event + + if not event.decoded_body: + raise BadRequestError('Invalid request body') + + ret = {'id': course_id} + body = BytesIO(event.decoded_body.encode()) + + def on_field(field): + field_name = field.field_name.decode().split('.') + + if len(field_name) > 1: + field_name, subfield_name = field_name + print(field_name, subfield_name) + else: + field_name, *_ = field_name + ret[field_name] = field.value + + parse_form( + event.headers, # type: ignore + body, + on_field=on_field, + on_file=on_field, + ) + + # print(ret.keys()) + data = FormData.model_validate(ret) + print(data) + return {} diff --git a/api.saladeaula.digital/app/routes/enrollments/__init__.py b/api.saladeaula.digital/app/routes/enrollments/__init__.py new file mode 100644 index 0000000..869c39f --- /dev/null +++ b/api.saladeaula.digital/app/routes/enrollments/__init__.py @@ -0,0 +1,19 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.exceptions import NotFoundError +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair + +from boto3clients import dynamodb_client +from config import ENROLLMENT_TABLE + +logger = Logger(__name__) +router = Router() +dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) + + +@router.get('/') +def get_enrollment(enrollment_id: str): + return dyn.collection.get_item( + KeyPair(enrollment_id, '0'), + exc_cls=NotFoundError, + ) diff --git a/api.saladeaula.digital/app/routes/orders/__init__.py b/api.saladeaula.digital/app/routes/orders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api.saladeaula.digital/app/routes/users/__init__.py b/api.saladeaula.digital/app/routes/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api.saladeaula.digital/app/routes/users/emails.py b/api.saladeaula.digital/app/routes/users/emails.py new file mode 100644 index 0000000..e69de29 diff --git a/api.saladeaula.digital/pyproject.toml b/api.saladeaula.digital/pyproject.toml index 5e907c6..8743b0c 100644 --- a/api.saladeaula.digital/pyproject.toml +++ b/api.saladeaula.digital/pyproject.toml @@ -8,12 +8,12 @@ dependencies = ["layercake"] [dependency-groups] dev = [ - "boto3-stubs[cognito-idp,essential]>=1.38.26", + "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", + "python-multipart>=0.0.20", + "requests-toolbelt>=1.0.0", "ruff>=0.9.1", "sqlite-utils>=3.38", "tqdm>=4.67.1", diff --git a/api.saladeaula.digital/pyrightconfig.json b/api.saladeaula.digital/pyrightconfig.json new file mode 100644 index 0000000..78cdf4e --- /dev/null +++ b/api.saladeaula.digital/pyrightconfig.json @@ -0,0 +1,3 @@ +{ + "extraPaths": ["app/"] +} diff --git a/api.saladeaula.digital/template.yaml b/api.saladeaula.digital/template.yaml index 3d456de..6dd1b02 100644 --- a/api.saladeaula.digital/template.yaml +++ b/api.saladeaula.digital/template.yaml @@ -5,6 +5,9 @@ Parameters: CourseTable: Type: String Default: saladeaula_courses + EnrollmentTable: + Type: String + Default: betaeducacao-prod-enrollments Globals: Function: @@ -23,6 +26,7 @@ Globals: POWERTOOLS_LOGGER_LOG_EVENT: true DYNAMODB_PARTITION_KEY: id COURSE_TABLE: !Ref CourseTable + ENROLLMENT_TABLE: !Ref EnrollmentTable Resources: HttpLog: @@ -58,8 +62,10 @@ Resources: LoggingConfig: LogGroup: !Ref HttpLog Policies: - - DynamoDBReadPolicy: + - DynamoDBCrudPolicy: TableName: !Ref CourseTable + - DynamoDBCrudPolicy: + TableName: !Ref EnrollmentTable Events: Preflight: Type: HttpApi diff --git a/api.saladeaula.digital/tests/conftest.py b/api.saladeaula.digital/tests/conftest.py new file mode 100644 index 0000000..052c8ad --- /dev/null +++ b/api.saladeaula.digital/tests/conftest.py @@ -0,0 +1,154 @@ +import base64 +import json +import os +from dataclasses import dataclass +from http import HTTPMethod +from urllib.parse import urlencode + +import jsonlines +import pytest + +PYTEST_TABLE_NAME = 'pytest' +PK = 'id' +SK = 'sk' + + +# https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_configure +def pytest_configure(): + os.environ['TZ'] = 'America/Sao_Paulo' + os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME + os.environ['DYNAMODB_PARTITION_KEY'] = PK + os.environ['DYNAMODB_SORT_KEY'] = SK + + +@dataclass +class LambdaContext: + function_name: str = 'test' + memory_limit_in_mb: int = 128 + invoked_function_arn: str = 'arn:aws:lambda:eu-west-1:809313241:function:test' + aws_request_id: str = '52fdfc07-2182-154f-163f-5f0f9a621d72' + + +class HttpApiProxy: + def __call__( + self, + raw_path: str, + method: str = HTTPMethod.GET, + body: dict | str | None = None, + *, + headers: dict = {}, + cookies: list[str] = [], + query_string_parameters: dict = {}, + is_base64_encoded: bool = True, + **kwargs, + ) -> dict: + if isinstance(body, dict): + body = json.dumps(body) + + if is_base64_encoded and body: + body = _base64_encode(body) + + return { + 'version': '2.0', + 'routeKey': '$default', + 'rawPath': raw_path, + 'rawQueryString': urlencode(query_string_parameters), + 'cookies': cookies, + 'headers': headers, + 'queryStringParameters': query_string_parameters, + 'requestContext': { + 'accountId': '123456789012', + 'apiId': 'api-id', + 'authorizer': {}, + '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': body, + 'pathParameters': {'parameter1': 'value1'}, + 'isBase64Encoded': is_base64_encoded, + 'stageVariables': {'stageVariable1': 'value1', 'stageVariable2': 'value2'}, + } + + +def _base64_encode(s: str) -> str | None: + if not s: + return None + return base64.b64encode(s.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 + + try: + client.create_table( + AttributeDefinitions=[ + {'AttributeName': PK, 'AttributeType': 'S'}, + {'AttributeName': SK, 'AttributeType': 'S'}, + ], + TableName=PYTEST_TABLE_NAME, + KeySchema=[ + {'AttributeName': PK, 'KeyType': 'HASH'}, + {'AttributeName': SK, 'KeyType': 'RANGE'}, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 123, + 'WriteCapacityUnits': 123, + }, + ) + except Exception: + pass + + yield 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 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/api.saladeaula.digital/tests/routes/__init__.py b/api.saladeaula.digital/tests/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api.saladeaula.digital/tests/routes/test_courses.py b/api.saladeaula.digital/tests/routes/test_courses.py new file mode 100644 index 0000000..eaf1359 --- /dev/null +++ b/api.saladeaula.digital/tests/routes/test_courses.py @@ -0,0 +1,51 @@ +from http import HTTPMethod, HTTPStatus + +from requests_toolbelt import MultipartEncoder + +from ..conftest import HttpApiProxy, LambdaContext + + +def test_get_course( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/courses/2a8963fc-4694-4fe2-953a-316d1b10f1f5', + method=HTTPMethod.GET, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.OK + + +def test_edit_course( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + with open('tests/sample.html', 'rb') as f: + m = MultipartEncoder( + fields={ + 'given_cert': 'true', + 'name': 'pytest', + 'access_period': '365', + 'cert.exp_interval': '360', + 'cert.rawfile': f, + } + ) + r = app.lambda_handler( + http_api_proxy( + raw_path='/courses/2a8963fc-4694-4fe2-953a-316d1b10f1f5', + method=HTTPMethod.POST, + headers={ + 'Content-Type': m.content_type, + }, + body=m.to_string().decode(), + is_base64_encoded=True, + ), + lambda_context, + ) diff --git a/api.saladeaula.digital/tests/sample.html b/api.saladeaula.digital/tests/sample.html new file mode 100644 index 0000000..7c7ecf8 --- /dev/null +++ b/api.saladeaula.digital/tests/sample.html @@ -0,0 +1,466 @@ + + + + + {{ ... }} + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

Certificamos que

+

{{ name }}

+

+ Portador(a) do CPF {{ cpf }}, concluiu o + curso de {{ ... }} com aproveitamento de + {{ score }}%. +

+

+ Realizado entre + {{ started_at }} e + {{ completed_at }}. +

+

+ Data de emissão do certificado: + {{ today }}. +

+ +
+
{{ name }}
+
+ + + +
+
+ Tiago Maciel dos Santos + +

Tiago Maciel do Santos

+

Responsável legal

+

EDUSEG LTDA

+

CNPJ 15.608.435/0001-90

+
+
+ +
+ © {{ year }} EDUSEG® Todos os direitos reservados. + CNPJ 15.608.435/0001-90 +
+
+
+ +
+
+

Conteúdo programático ministrado

+
    +
  • ...
  • +
+
+ +
+
+

Treinamento ministrado

+

{{ ... }}

+
+ +
+

Carga horária

+

{{ hours }} horas

+
+ +
+

Validade

+

{{ expires_at }}

+
+ +
+

Instrutora e responsável técnico

+
+ +

+ Francis Ricardo Baretta +

+

CPF 039.539.409-02

+

Eng. de Segurança no Trabalho Eng. Eletricista

+

CREA/SC 126693-0

+
+
+
+
+ + diff --git a/api.saladeaula.digital/tests/seeds.jsonl b/api.saladeaula.digital/tests/seeds.jsonl new file mode 100644 index 0000000..c4ceca5 --- /dev/null +++ b/api.saladeaula.digital/tests/seeds.jsonl @@ -0,0 +1 @@ +{"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest" } \ No newline at end of file diff --git a/api.saladeaula.digital/uv.lock b/api.saladeaula.digital/uv.lock index e4167f0..df71235 100644 --- a/api.saladeaula.digital/uv.lock +++ b/api.saladeaula.digital/uv.lock @@ -21,12 +21,12 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "boto3-stubs", extra = ["cognito-idp", "essential"] }, + { name = "boto3-stubs", extra = ["essential"] }, { name = "jsonlines" }, - { name = "psycopg2-binary" }, - { name = "pycouchdb" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "python-multipart" }, + { name = "requests-toolbelt" }, { name = "ruff" }, { name = "sqlite-utils" }, { name = "tqdm" }, @@ -37,12 +37,12 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] [package.metadata.requires-dev] dev = [ - { name = "boto3-stubs", extras = ["cognito-idp", "essential"], specifier = ">=1.38.26" }, + { 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 = "python-multipart", specifier = ">=0.0.20" }, + { name = "requests-toolbelt", specifier = ">=1.0.0" }, { name = "ruff", specifier = ">=0.9.1" }, { name = "sqlite-utils", specifier = ">=3.38" }, { name = "tqdm", specifier = ">=4.67.1" }, @@ -166,9 +166,6 @@ wheels = [ ] [package.optional-dependencies] -cognito-idp = [ - { name = "mypy-boto3-cognito-idp" }, -] essential = [ { name = "mypy-boto3-cloudformation" }, { name = "mypy-boto3-dynamodb" }, @@ -273,15 +270,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] -[[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.3" @@ -568,6 +556,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "joserfc" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/79/d63a882c0212e95f3ba8863a115e5ca9a5d39413b02273ddce72058e717f/joserfc-1.3.4.tar.gz", hash = "sha256:67d8413c501c239f65eefad5ae685cfbfc401aa63289fc409ef7cc331b007227", size = 197787, upload-time = "2025-09-21T15:49:34.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/78/beb171f0caac81f51695f43c87c0c20b3ce2a190cf74a3fbb0b9afe03b45/joserfc-1.3.4-py3-none-any.whl", hash = "sha256:30c845c58d441cfe32d08ac35e437812481ca8155373873b7abf80224bf601c0", size = 75638, upload-time = "2025-09-21T15:49:33.14Z" }, +] + [[package]] name = "jsonlines" version = "4.0.0" @@ -603,6 +603,7 @@ dependencies = [ { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, + { name = "joserfc" }, { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, @@ -610,7 +611,6 @@ dependencies = [ { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, - { name = "pyjwt" }, { name = "pytz" }, { name = "requests" }, { name = "smart-open", extra = ["s3"] }, @@ -626,6 +626,7 @@ requires-dist = [ { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, { name = "glom", specifier = ">=24.11.0" }, + { name = "joserfc", specifier = ">=1.2.2" }, { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, @@ -633,7 +634,6 @@ requires-dist = [ { 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 = "requests", specifier = ">=2.32.3" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, @@ -674,15 +674,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/f8/0f74d0a5c5a5e6cd7bfe2c74c3024ea53964e0bf808eeb71db18ed2cb665/mypy_boto3_cloudformation-1.40.24-py3-none-any.whl", hash = "sha256:dfd4f8d72005373ff9df20e779524f8ea24c1c1ba051c663f7ce855da349ee27", size = 69875, upload-time = "2025-09-04T19:24:35.644Z" }, ] -[[package]] -name = "mypy-boto3-cognito-idp" -version = "1.40.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/27/315b11356b93b408fc1bd446b5329a290fe21484c8a559a43c99de66a154/mypy_boto3_cognito_idp-1.40.14.tar.gz", hash = "sha256:83bf63a6d7c16cafd6b947900cc9c4a2010d674cac7f550a7cc15ad5fce55a45", size = 52172, upload-time = "2025-08-20T19:27:17.857Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/1a/d2a57cff6c0e93c9f580ad1c903bc40d80c474b58f5b6414ffc4e3b7d830/mypy_boto3_cognito_idp-1.40.14-py3-none-any.whl", hash = "sha256:27ff55e982585b2adc40b24baeba9ac7c61dcd6ee5ca90466d461847e53e0d9a", size = 57994, upload-time = "2025-08-20T19:27:04.525Z" }, -] - [[package]] name = "mypy-boto3-dynamodb" version = "1.40.20" @@ -849,38 +840,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/dd/464bd739bacb3b745a1c93bc15f20f0b1e27f0a64ec693367794b398673b/psycopg_binary-3.2.10-cp314-cp314-win_amd64.whl", hash = "sha256:d5c6a66a76022af41970bf19f51bc6bf87bd10165783dd1d40484bfd87d6b382", size = 2973554, upload-time = "2025-09-08T09:12:05.884Z" }, ] -[[package]] -name = "psycopg2-binary" -version = "2.9.10" -source = { registry = "https://pypi.org/simple" } -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/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]] name = "pycparser" version = "2.23" @@ -983,15 +942,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, -] - [[package]] name = "pytest" version = "8.4.2" @@ -1043,6 +993,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + [[package]] name = "pytz" version = "2025.2" @@ -1067,6 +1026,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + [[package]] name = "ruff" version = "0.13.1" diff --git a/id.saladeaula.digital/app/boto3clients.py b/id.saladeaula.digital/app/boto3clients.py index 3aa0e9e..7d2a8c1 100644 --- a/id.saladeaula.digital/app/boto3clients.py +++ b/id.saladeaula.digital/app/boto3clients.py @@ -1,19 +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(): - running_sam_local = os.getenv('AWS_SAM_LOCAL') - if os.getenv('AWS_LAMBDA_FUNCTION_NAME') and not running_sam_local: +def get_dynamodb_client() -> DynamoDBClient: + if os.getenv('AWS_LAMBDA_FUNCTION_NAME'): return boto3.client('dynamodb') - dockerhost = 'host.docker.internal' - localhost = '127.0.0.1' - host = dockerhost if running_sam_local else localhost - - return boto3.client('dynamodb', endpoint_url=f'http://{host}:8000') + 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/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py b/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py index 91d6bd2..08ea4e7 100644 --- a/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py @@ -157,7 +157,6 @@ class AuthorizationServer(oauth2.AuthorizationServer): body, headers, ): - logger.debug('handle_response', status=status, body=body) return Response( status_code=status, body=body, diff --git a/id.saladeaula.digital/client/app/entry.server.tsx b/id.saladeaula.digital/client/app/entry.server.tsx index 78cb542..bb1ec05 100644 --- a/id.saladeaula.digital/client/app/entry.server.tsx +++ b/id.saladeaula.digital/client/app/entry.server.tsx @@ -1,43 +1,43 @@ -import { isbot } from "isbot"; -import { renderToReadableStream } from "react-dom/server"; -import type { AppLoadContext, EntryContext } from "react-router"; -import { ServerRouter } from "react-router"; +import { isbot } from 'isbot' +import { renderToReadableStream } from 'react-dom/server' +import type { AppLoadContext, EntryContext } from 'react-router' +import { ServerRouter } from 'react-router' export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, - _loadContext: AppLoadContext, + _loadContext: AppLoadContext ) { - let shellRendered = false; - const userAgent = request.headers.get("user-agent"); + let shellRendered = false + const userAgent = request.headers.get('user-agent') const body = await renderToReadableStream( , { onError(error: unknown) { - responseStatusCode = 500; + responseStatusCode = 500 // Log streaming rendering errors from inside the shell. Don't log // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { - console.error(error); + console.error(error) } - }, - }, - ); - shellRendered = true; + } + } + ) + shellRendered = true // Ensure requests from bots and SPA Mode renders wait for all content to load before responding // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) { - await body.allReady; + await body.allReady } - responseHeaders.set("Content-Type", "text/html"); + responseHeaders.set('Content-Type', 'text/html') return new Response(body, { headers: responseHeaders, - status: responseStatusCode, - }); + status: responseStatusCode + }) } diff --git a/id.saladeaula.digital/client/app/routes/index.tsx b/id.saladeaula.digital/client/app/routes/index.tsx index 3fac14d..93e91ed 100644 --- a/id.saladeaula.digital/client/app/routes/index.tsx +++ b/id.saladeaula.digital/client/app/routes/index.tsx @@ -117,7 +117,7 @@ export default function Index({}: Route.ComponentProps) {
- React Router + EDUSEG®
diff --git a/id.saladeaula.digital/pyproject.toml b/id.saladeaula.digital/pyproject.toml index dbe9e4e..9446352 100644 --- a/id.saladeaula.digital/pyproject.toml +++ b/id.saladeaula.digital/pyproject.toml @@ -4,9 +4,7 @@ version = "0.1.0" description = "Add your description here" readme = "" requires-python = ">=3.13" -dependencies = [ - "layercake", -] +dependencies = ["layercake"] [dependency-groups] dev = [ @@ -14,6 +12,7 @@ dev = [ "pytest>=8.4.1", "ruff>=0.12.1", "pytest-cov>=6.2.1", + "boto3-stubs[essential]>=1.40.44", ] diff --git a/id.saladeaula.digital/tests/routes/test_revoke.py b/id.saladeaula.digital/tests/routes/test_revoke.py index 4cd4b28..3ed0e1c 100644 --- a/id.saladeaula.digital/tests/routes/test_revoke.py +++ b/id.saladeaula.digital/tests/routes/test_revoke.py @@ -1,5 +1,4 @@ import json -import pprint from base64 import b64encode from http import HTTPMethod, HTTPStatus from urllib.parse import urlencode @@ -44,7 +43,7 @@ def token( return json.loads(r['body']) -def test_token( +def test_revoke( app, token, seeds, diff --git a/id.saladeaula.digital/tests/routes/test_token.py b/id.saladeaula.digital/tests/routes/test_token.py index 6f62066..b94b3c2 100644 --- a/id.saladeaula.digital/tests/routes/test_token.py +++ b/id.saladeaula.digital/tests/routes/test_token.py @@ -7,6 +7,10 @@ from layercake.dynamodb import DynamoDBPersistenceLayer from ..conftest import HttpApiProxy, LambdaContext +CLIENT_ID = 'd72d4005-1fa7-4430-9754-80d5e2487bb6' +CLIENT_SECRET = '1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W' +AUTH = b64encode(f'{CLIENT_ID}:{CLIENT_SECRET}'.encode()).decode() + def test_token( app, @@ -91,22 +95,19 @@ def test_refresh_token( http_api_proxy: HttpApiProxy, lambda_context: LambdaContext, ): - client_id = 'd72d4005-1fa7-4430-9754-80d5e2487bb6' - client_secret = '1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W' - auth = b64encode(f'{client_id}:{client_secret}'.encode()).decode() r = app.lambda_handler( http_api_proxy( raw_path='/token', method=HTTPMethod.POST, headers={ - 'Authorization': f'Basic {auth}', + 'Authorization': f'Basic {AUTH}', 'Content-Type': 'application/x-www-form-urlencoded', }, body=urlencode( { 'grant_type': 'refresh_token', 'refresh_token': 'CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy', - 'client_id': client_id, + 'client_id': CLIENT_ID, } ), ), diff --git a/id.saladeaula.digital/tests/seeds.jsonl b/id.saladeaula.digital/tests/seeds.jsonl index b0a64e4..3924801 100644 --- a/id.saladeaula.digital/tests/seeds.jsonl +++ b/id.saladeaula.digital/tests/seeds.jsonl @@ -15,7 +15,7 @@ // User data {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"} -// {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"} +{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"} {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": "openid profile email offline_access read:users read:courses"} {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SESSION#36af142e-9f6d-49d3-bfe9-6a6bd6ab2712", "created_at": "2025-09-17T13:44:34.544491-03:00", "ttl": 1760719474} diff --git a/id.saladeaula.digital/uv.lock b/id.saladeaula.digital/uv.lock index c9e5331..51a83e2 100644 --- a/id.saladeaula.digital/uv.lock +++ b/id.saladeaula.digital/uv.lock @@ -119,6 +119,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/13/0eb850c821a976346a905370bb30c86da7edc2bbc3977c445fffc6306032/boto3-1.38.46-py3-none-any.whl", hash = "sha256:9c8e88a32a6465e5905308708cff5b17547117f06982908bdfdb0108b4a65079", size = 139925, upload-time = "2025-06-27T20:18:15.366Z" }, ] +[[package]] +name = "boto3-stubs" +version = "1.40.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore-stubs" }, + { name = "types-s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/65/7996dd7e6035ea8ede82169403a7f0a6e5a60fadbd79ecfd042b62dc888f/boto3_stubs-1.40.44.tar.gz", hash = "sha256:c49030d3c8a1f719e2ea283d7eff834037406ef41ecad47490d780ae8a15ff5c", size = 100839, upload-time = "2025-10-02T20:37:35.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/79/e389dd7ca833f93c06921c2d57650784446c9a18aea091dd8dd1ee25ee23/boto3_stubs-1.40.44-py3-none-any.whl", hash = "sha256:0fa09cca77e4b3be0222ece396ef8eb99cfb495e48e8b33c8e497d910597e376", size = 69687, upload-time = "2025-10-02T20:37:30.366Z" }, +] + +[package.optional-dependencies] +essential = [ + { name = "mypy-boto3-cloudformation" }, + { name = "mypy-boto3-dynamodb" }, + { name = "mypy-boto3-ec2" }, + { name = "mypy-boto3-lambda" }, + { name = "mypy-boto3-rds" }, + { name = "mypy-boto3-s3" }, + { name = "mypy-boto3-sqs" }, +] + [[package]] name = "botocore" version = "1.38.46" @@ -133,6 +157,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/00/dd90b7a0255587ba1c9754d32a221adb4a9022f181df3eef401b0b9fadfc/botocore-1.38.46-py3-none-any.whl", hash = "sha256:89ca782ffbf2e8769ca9c89234cfa5ca577f1987d07d913ee3c68c4776b1eb5b", size = 13736872, upload-time = "2025-06-27T20:18:00.901Z" }, ] +[[package]] +name = "botocore-stubs" +version = "1.40.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-awscrt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/94/16f8e1f41feaa38f1350aa5a4c60c5724b6c8524ca0e6c28523bf5070e74/botocore_stubs-1.40.33.tar.gz", hash = "sha256:89c51ae0b28d9d79fde8c497cf908ddf872ce027d2737d4d4ba473fde9cdaa82", size = 42742, upload-time = "2025-09-17T20:25:56.388Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/7b/6d8fe12a955b16094460e89ea7c4e063f131f4b3bd461b96bcd625d0c79e/botocore_stubs-1.40.33-py3-none-any.whl", hash = "sha256:ad21fee32cbdc7ad4730f29baf88424c7086bf88a745f8e43660ca3e9a7e5f89", size = 66843, upload-time = "2025-09-17T20:25:54.052Z" }, +] + [[package]] name = "camel-converter" version = "4.0.1" @@ -387,6 +423,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "boto3-stubs", extra = ["essential"] }, { name = "jsonlines" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -398,6 +435,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] [package.metadata.requires-dev] dev = [ + { name = "boto3-stubs", extras = ["essential"], specifier = ">=1.40.44" }, { name = "jsonlines", specifier = ">=4.0.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-cov", specifier = ">=6.2.1" }, @@ -528,6 +566,69 @@ 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-cloudformation" +version = "1.40.44" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/2c/140b6b0d74afa4854edb3b3e640ff96c711270f27711ad1325cecc5661c1/mypy_boto3_cloudformation-1.40.44.tar.gz", hash = "sha256:3d82f5504382c86ad195a1b80a2a82f73587c37e1b636864ebb85dd43bd79a5b", size = 57870, upload-time = "2025-10-02T20:32:01.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/38/12301080cc5004593b8593c0cc7404c13d702ac1c15d4e0ccfacd1f4f416/mypy_boto3_cloudformation-1.40.44-py3-none-any.whl", hash = "sha256:64c8fe58ab7661fbb0bdea07c7375d3ebc3875760140feb6ad8f591a08a22647", size = 69896, upload-time = "2025-10-02T20:31:56.896Z" }, +] + +[[package]] +name = "mypy-boto3-dynamodb" +version = "1.40.44" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/63/31bc3e1d890541284df0d5569e61398d563c1982724ec4f19c08142e7048/mypy_boto3_dynamodb-1.40.44.tar.gz", hash = "sha256:58fa3a638b1caef5644b60f5894e1182a2951feb30a3dc6dedb34e1b0c9ded99", size = 47947, upload-time = "2025-10-02T20:37:27.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/52/1e6230eab337062f19fcfe1bd39cff0cc841b9a48c37318fd2b4d66b07a3/mypy_boto3_dynamodb-1.40.44-py3-none-any.whl", hash = "sha256:ab978a9d24997d513c5e35bf4aae9b3dfe4f8482a13799180ecefbb1dc93d271", size = 56994, upload-time = "2025-10-02T20:31:56.823Z" }, +] + +[[package]] +name = "mypy-boto3-ec2" +version = "1.40.40" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/390fa5c21fd96d9aafdd2174b3522bad867f852b2fcc3900d8c196ad8f26/mypy_boto3_ec2-1.40.40.tar.gz", hash = "sha256:23e60e95a0e14c814dddf77c7f931cb4c10511034a47e972313c8f5e23ea691a", size = 409172, upload-time = "2025-09-26T19:25:12.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/36/46db406bbc399d17f5ad17d71f68978a5f33d4772183c386a95ab3f5a8e9/mypy_boto3_ec2-1.40.40-py3-none-any.whl", hash = "sha256:4ffc151d4a411f6805e87656c567fdeb5c95728636c4912d7faf4ab476adf24a", size = 398392, upload-time = "2025-09-26T19:25:08.854Z" }, +] + +[[package]] +name = "mypy-boto3-lambda" +version = "1.40.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/4f/63def7a5be630e8d39186594b01fee86f2e6dcbca3e0b0e80a3ea90bc4ae/mypy_boto3_lambda-1.40.7.tar.gz", hash = "sha256:e8bedf03a67fade5db861fe902df063064292352eed5f785f74cd0e591948db9", size = 42491, upload-time = "2025-08-11T19:30:54.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ef/ab7a0fc83b8f3c39145cbce4a8bf5326e153157f0edcc939b4570e0b9f9e/mypy_boto3_lambda-1.40.7-py3-none-any.whl", hash = "sha256:398c9dd051278430168e907b6b6c2078a3a1bca3948c2bf4d47eda8d7da28573", size = 49058, upload-time = "2025-08-11T19:30:51.675Z" }, +] + +[[package]] +name = "mypy-boto3-rds" +version = "1.40.42" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/de/a4bace6b75251c79fdefa0335c72c86ee68e2c0bfdb1aef36607241365bc/mypy_boto3_rds-1.40.42.tar.gz", hash = "sha256:77d8ce076a42400f87154c4e0c4c298358cbb12bc6a02ec41fb6daa97539d132", size = 85576, upload-time = "2025-09-30T19:43:21.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/5b/902fade65a8de58c08895f4a54a524a068eb25bedffa32564e7ba8959f1d/mypy_boto3_rds-1.40.42-py3-none-any.whl", hash = "sha256:686f5fd95de3efc3b0a041e3bbd3304d5674703d36adeb944f3888207019b0f8", size = 92009, upload-time = "2025-09-30T19:43:17.856Z" }, +] + +[[package]] +name = "mypy-boto3-s3" +version = "1.40.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/b8/55d21ed9ca479df66d9892212ba7d7977850ef17aa80a83e3f11f31190fd/mypy_boto3_s3-1.40.26.tar.gz", hash = "sha256:8d2bfd1052894d0e84c9fb9358d838ba0eed0265076c7dd7f45622c770275c99", size = 75948, upload-time = "2025-09-08T20:12:21.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/a5/dba3384423834009bdd41c7021de5c663468a0e7bc4071cb301721e52a99/mypy_boto3_s3-1.40.26-py3-none-any.whl", hash = "sha256:6d055d16ef89a0133ade92f6b4f09603e4acc31a0f5e8f846edf4eb48f17b5a7", size = 82762, upload-time = "2025-09-08T20:12:19.338Z" }, +] + +[[package]] +name = "mypy-boto3-sqs" +version = "1.40.35" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/be/27ead4078dc1faa9c6c1916e0f7cfdb102925591eaab153a496640250541/mypy_boto3_sqs-1.40.35.tar.gz", hash = "sha256:c11f95ee72bddb84f7fecf3000372e01547f36737064b785f1cf34191b87e03f", size = 23583, upload-time = "2025-09-19T19:42:27.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/12/383dbfd97656f0271e4b7f047be211b49ee3daa2d5d78869b8c2993c906b/mypy_boto3_sqs-1.40.35-py3-none-any.whl", hash = "sha256:719967d5973ab99a1298381023add28da45ee0dca2b5bf4969b7b5e4fc1a5db8", size = 33779, upload-time = "2025-09-19T19:42:23.975Z" }, +] + [[package]] name = "orjson" version = "3.10.18" @@ -906,6 +1007,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "types-awscrt" +version = "0.27.6" +source = { registry = "https://pypi.org/simple" } +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/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 = "types-s3transfer" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/c5/23946fac96c9dd5815ec97afd1c8ad6d22efa76c04a79a4823f2f67692a5/types_s3transfer-0.13.1.tar.gz", hash = "sha256:ce488d79fdd7d3b9d39071939121eca814ec65de3aa36bdce1f9189c0a61cc80", size = 14181, upload-time = "2025-08-31T16:57:06.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/dc/b3f9b5c93eed6ffe768f4972661250584d5e4f248b548029026964373bcd/types_s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:4ff730e464a3fd3785b5541f0f555c1bd02ad408cf82b6b7a95429f6b0d26b4a", size = 19617, upload-time = "2025-08-31T16:57:05.73Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.0" diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 52b30be..621aa32 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -25,8 +25,8 @@ dependencies = [ "unidecode>=1.4.0", "authlib>=1.6.1", "passlib>=1.7.4", - "pyjwt>=2.10.1", "psycopg[binary]>=3.2.9", + "joserfc>=1.2.2", ] [dependency-groups] diff --git a/layercake/uv.lock b/layercake/uv.lock index c471302..8cc0377 100644 --- a/layercake/uv.lock +++ b/layercake/uv.lock @@ -675,7 +675,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.13" +version = "0.9.14" source = { editable = "." } dependencies = [ { name = "arnparse" }, @@ -684,6 +684,7 @@ dependencies = [ { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, + { name = "joserfc" }, { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, @@ -691,7 +692,6 @@ dependencies = [ { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, - { name = "pyjwt" }, { name = "pytz" }, { name = "requests" }, { name = "smart-open", extra = ["s3"] }, @@ -718,6 +718,7 @@ requires-dist = [ { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, { name = "glom", specifier = ">=24.11.0" }, + { name = "joserfc", specifier = ">=1.2.2" }, { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, @@ -725,7 +726,6 @@ requires-dist = [ { 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 = "requests", specifier = ">=2.32.3" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, @@ -1233,15 +1233,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, -] - [[package]] name = "pyparsing" version = "3.2.3"