This commit is contained in:
2025-10-03 19:31:41 -03:00
parent 48ed8d8345
commit 5ae2128dee
29 changed files with 996 additions and 168 deletions

View File

@@ -1,6 +1,5 @@
import json import json
import os import os
from datetime import date
from functools import partial from functools import partial
from typing import Any from typing import Any
@@ -9,26 +8,13 @@ from aws_lambda_powertools.event_handler.api_gateway import (
APIGatewayHttpResolver, APIGatewayHttpResolver,
CORSConfig, 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.logging import correlation_paths
from aws_lambda_powertools.shared.json_encoder import Encoder
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from api_gateway import JSONResponse from api_gateway import JSONResponse
from boto3clients import dynamodb_client from json_encoder import JSONEncoder
from config import COURSE_TABLE from routes import courses, enrollments
dyn = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
class JSONEncoder(Encoder):
def default(self, obj):
if isinstance(obj, date):
return obj.isoformat()
return super().default(obj)
tracer = Tracer() tracer = Tracer()
logger = Logger(__name__) logger = Logger(__name__)
@@ -44,27 +30,8 @@ app = APIGatewayHttpResolver(
debug='AWS_SAM_LOCAL' in os.environ, debug='AWS_SAM_LOCAL' in os.environ,
serializer=partial(json.dumps, separators=(',', ':'), cls=JSONEncoder), serializer=partial(json.dumps, separators=(',', ':'), cls=JSONEncoder),
) )
app.include_router(courses.router, prefix='/courses')
app.include_router(enrollments.router, prefix='/enrollments')
@app.get('/users/<user_id>')
@tracer.capture_method
def get_user(user_id: str):
return {'id': user_id}
@app.get('/users/<user_id>/emails')
@tracer.capture_method
def get_emails(user_id: str):
return [{'email': 'sergio@somosbeta.com.br'}]
@app.get('/courses/<course_id>')
@tracer.capture_method
def get_course(course_id: str):
return dyn.collection.get_item(
KeyPair(course_id, '0'),
exc_cls=NotFoundError,
)
@app.exception_handler(ServiceError) @app.exception_handler(ServiceError)

View File

@@ -8,14 +8,13 @@ if TYPE_CHECKING:
else: else:
DynamoDBClient = object DynamoDBClient = object
AWS_SAM_LOCAL = os.getenv('AWS_SAM_LOCAL')
def get_dynamodb_client() -> DynamoDBClient: def get_dynamodb_client() -> DynamoDBClient:
if not AWS_SAM_LOCAL: if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):
return boto3.client('dynamodb') 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') return boto3.client('dynamodb', endpoint_url=f'http://{host}:8000')

View File

@@ -1,4 +1,5 @@
import os import os
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore 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 COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore

View File

@@ -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)

View File

@@ -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('/<course_id>')
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('/<course_id>')
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 {}

View File

@@ -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('/<enrollment_id>')
def get_enrollment(enrollment_id: str):
return dyn.collection.get_item(
KeyPair(enrollment_id, '0'),
exc_cls=NotFoundError,
)

View File

@@ -8,12 +8,12 @@ dependencies = ["layercake"]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"boto3-stubs[cognito-idp,essential]>=1.38.26", "boto3-stubs[essential]>=1.38.26",
"jsonlines>=4.0.0", "jsonlines>=4.0.0",
"psycopg2-binary>=2.9.10",
"pycouchdb>=1.16.0",
"pytest>=8.3.4", "pytest>=8.3.4",
"pytest-cov>=6.0.0", "pytest-cov>=6.0.0",
"python-multipart>=0.0.20",
"requests-toolbelt>=1.0.0",
"ruff>=0.9.1", "ruff>=0.9.1",
"sqlite-utils>=3.38", "sqlite-utils>=3.38",
"tqdm>=4.67.1", "tqdm>=4.67.1",

View File

@@ -0,0 +1,3 @@
{
"extraPaths": ["app/"]
}

View File

@@ -5,6 +5,9 @@ Parameters:
CourseTable: CourseTable:
Type: String Type: String
Default: saladeaula_courses Default: saladeaula_courses
EnrollmentTable:
Type: String
Default: betaeducacao-prod-enrollments
Globals: Globals:
Function: Function:
@@ -23,6 +26,7 @@ Globals:
POWERTOOLS_LOGGER_LOG_EVENT: true POWERTOOLS_LOGGER_LOG_EVENT: true
DYNAMODB_PARTITION_KEY: id DYNAMODB_PARTITION_KEY: id
COURSE_TABLE: !Ref CourseTable COURSE_TABLE: !Ref CourseTable
ENROLLMENT_TABLE: !Ref EnrollmentTable
Resources: Resources:
HttpLog: HttpLog:
@@ -58,8 +62,10 @@ Resources:
LoggingConfig: LoggingConfig:
LogGroup: !Ref HttpLog LogGroup: !Ref HttpLog
Policies: Policies:
- DynamoDBReadPolicy: - DynamoDBCrudPolicy:
TableName: !Ref CourseTable TableName: !Ref CourseTable
- DynamoDBCrudPolicy:
TableName: !Ref EnrollmentTable
Events: Events:
Preflight: Preflight:
Type: HttpApi Type: HttpApi

View File

@@ -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

View File

@@ -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,
)

View File

@@ -0,0 +1,466 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>{{ ... }}</title>
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
<style>
html,
body,
div,
h1,
h2,
ul,
p,
a {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
@page {
size: A4 landscape;
margin: 0;
}
html {
font-family: Arial;
font-size: 13pt;
line-height: 1.4;
}
section {
width: 29.7cm;
height: 21cm;
break-after: page;
box-sizing: border-box;
}
strong {
font-weight: bold;
}
#cover {
background-color: #a7e400;
justify-content: center;
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
}
#cover .content {
background: #fff;
border-radius: 10px;
height: 100%;
padding: 2rem 4rem;
justify-content: center;
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
}
#cover .header {
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
}
#cover .header > div {
display: flex;
align-items: center;
}
#cover h1 {
font-weight: bolder;
font-size: 24pt;
}
#cover .signatures {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
#cover .copy {
text-align: center;
}
.sign1 {
width: 250px;
text-align: center;
border-top: #000 solid 1px;
}
.sign2 {
position: relative;
}
.sign2 img {
position: absolute;
top: -55px;
left: -55px;
width: 300px;
}
#back {
background-color: white;
display: flex;
flex-direction: row;
gap: 0.5rem;
padding: 5rem;
}
#back img.trainer {
width: 250px;
padding-bottom: 5px;
border-bottom: 1px solid #000;
}
#back h1,
#back h2 {
font-weight: bold;
font-size: 16pt;
}
#back ul {
padding-left: 1rem;
}
.space-y-0\.5 > :not(:last-child) {
margin-bottom: 0.125rem;
}
.space-y-2\.5 > :not(:last-child) {
margin-bottom: 0.625rem;
}
.space-y-5 > :not(:last-child) {
margin-bottom: 1.5rem;
}
</style>
</head>
<body>
<section id="cover">
<div class="content">
<div class="header">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1072.73 329.6"
style="width: 14rem"
>
<g>
<g>
<path
fill="#8cd366"
d="M152.18,217.62l-68.61-30.91c-1.88-1.2-4.28-1.2-6.16,0l-68.61,30.91c-3.81,2.43-8.8-.3-8.8-4.82V8.98C0,5.82,2.56,3.26,5.72,3.26h149.54c3.16,0,5.72,2.56,5.72,5.72v203.81c0,4.52-5,7.26-8.8,4.82Z"
></path>
<path
fill="#2e3524"
d="M93.97,74.01H26.61v20.16h67.36v-20.16Z"
></path>
<path
fill="#2e3524"
d="M107.44,111.16H26.61v26.8h80.83v-26.8Z"
></path>
<path
fill="#2e3524"
d="M107.44,30.27H26.61v26.8h80.83v-26.8Z"
></path>
<path
fill="#2e3524"
d="M134.38,131.23c0-3.72-3.02-6.73-6.73-6.73s-6.73,3.02-6.73,6.73,3.02,6.73,6.73,6.73,6.73-3.02,6.73-6.73Z"
></path>
</g>
<g>
<path
fill="#2E3524"
d="M244.7,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
></path>
<path
fill="#2E3524"
d="M362.72,3.24h57.79c10.71,0,20.47,2.35,29.29,7.06,8.83,4.7,15.79,11.18,20.87,19.39,5.08,8.21,7.63,17.45,7.63,27.67v214.88c0,10.22-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.83,4.7-18.73,7.08-29.7,7.08h-57.79V3.24ZM427.55,283.88c1.74-1.87,2.6-4.18,2.6-6.86V52.56c-.26-2.69-1.34-4.97-3.22-6.86-1.88-1.87-4.15-2.83-6.82-2.83h-14v243.85h14.41c2.93,0,5.27-.94,7.01-2.83h.02Z"
></path>
<path
fill="#2E3524"
d="M531.5,322.49c-8.71-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67V3.24h48.15v279.41c0,2.69.93,4.99,2.82,6.86,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.86V3.24h48.16v272.21c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.7-18.73,7.08-29.7,7.08s-20.8-2.35-29.5-7.08l.05-.02Z"
></path>
<path
fill="#2E3524"
d="M672.79,322.49c-8.7-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67v-78.75h48.16v85.95c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-77.88c0-5.66-2.22-10.3-6.63-13.94-4.41-3.62-11.57-8.02-21.47-13.13-8.3-4.3-15.05-8.14-20.27-11.52-5.22-3.36-9.71-7.87-13.45-13.54-3.75-5.66-5.63-12.24-5.63-19.8V54.12c0-10.22,2.53-19.44,7.63-27.67,5.08-8.21,11.97-14.66,20.68-19.39,8.68-4.7,18.53-7.06,29.5-7.06s20.87,2.35,29.69,7.06c8.83,4.7,15.72,11.18,20.68,19.39,4.96,8.21,7.42,17.45,7.42,27.67v71.09h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v69.79c0,6.19,2.34,11.26,7.04,15.14,4.67,3.91,12.24,8.83,22.68,14.74,8.04,4.32,14.57,8.09,19.68,11.3,5.08,3.24,9.37,7.46,12.83,12.72,3.48,5.26,5.22,11.26,5.22,17.98v86.83c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.71-18.72,7.08-29.7,7.08s-20.8-2.35-29.5-7.08Z"
></path>
<path
fill="#2E3524"
d="M784.56,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
></path>
<path
fill="#2E3524"
d="M920.63,322.49c-5.63-4.18-10.11-10.1-13.45-17.76-3.34-7.68-5.01-16.49-5.01-26.45V53.71c0-9.96,2.53-19.06,7.63-27.26,5.08-8.21,12.05-14.66,20.87-19.39,8.83-4.7,18.6-7.06,29.32-7.06s20.54,2.35,29.5,7.06c8.97,4.7,15.91,11.18,20.87,19.39,4.96,8.21,7.42,17.3,7.42,27.26v94.51h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v231.36c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-46.03h-11.64v-51.29h59.8v145.4h-48.16v-14.14c-2.96,5.4-6.82,9.48-11.64,12.31-4.82,2.83-10.83,4.25-18.06,4.25s-13.64-2.09-19.27-6.26l-.02-.02Z"
></path>
<path
fill="#2E3524"
d="M1053.27,25.05h-6.13l-.06-3.69h5.48c.83-.02,1.61-.15,2.33-.4.72-.27,1.3-.64,1.73-1.14.44-.51.65-1.14.65-1.87,0-.93-.16-1.67-.48-2.22-.3-.55-.83-.94-1.59-1.16-.74-.25-1.74-.37-3.01-.37h-3.78v20.42h-4.12V10.54h7.9c1.87,0,3.49.27,4.86.82,1.38.53,2.44,1.34,3.18,2.44.76,1.08,1.14,2.43,1.14,4.06,0,1.02-.24,1.93-.71,2.73-.47.8-1.17,1.49-2.1,2.07-.91.57-2.03,1.03-3.35,1.39-.06,0-.12.07-.2.2-.06.13-.11.2-.17.2-.32.19-.53.33-.63.43-.08.08-.16.12-.25.14-.08.02-.31.03-.68.03ZM1052.99,25.05l.6-2.81c2.95,0,4.97.64,6.05,1.93,1.08,1.27,1.62,2.89,1.62,4.86v1.53c0,.7.03,1.37.08,2.02.08.62.21,1.15.4,1.59v.45h-4.23c-.19-.49-.3-1.19-.34-2.1-.02-.91-.03-1.57-.03-1.99v-1.48c0-1.38-.31-2.39-.94-3.04s-1.69-.97-3.21-.97ZM1035.75,22.92c0,2.52.43,4.87,1.28,7.04.87,2.16,2.08,4.05,3.64,5.68,1.55,1.61,3.34,2.87,5.37,3.78,2.05.89,4.22,1.33,6.53,1.33s4.51-.44,6.53-1.33c2.03-.91,3.8-2.17,5.34-3.78,1.53-1.63,2.74-3.52,3.61-5.68.87-2.18,1.31-4.52,1.31-7.04s-.44-4.86-1.31-7.01c-.87-2.16-2.07-4.04-3.61-5.65-1.53-1.61-3.31-2.86-5.34-3.75-2.03-.91-4.2-1.36-6.53-1.36s-4.49.45-6.53,1.36c-2.03.89-3.81,2.14-5.37,3.75-1.55,1.61-2.77,3.49-3.64,5.65-.85,2.16-1.28,4.5-1.28,7.01ZM1032.4,22.92c0-3.01.52-5.8,1.56-8.38,1.04-2.57,2.49-4.82,4.34-6.73,1.86-1.93,4-3.43,6.42-4.49,2.44-1.08,5.06-1.62,7.84-1.62s5.39.54,7.81,1.62c2.44,1.06,4.58,2.56,6.42,4.49,1.86,1.91,3.31,4.16,4.35,6.73,1.06,2.57,1.59,5.37,1.59,8.38s-.53,5.8-1.59,8.38c-1.04,2.57-2.49,4.84-4.35,6.79-1.83,1.93-3.97,3.44-6.42,4.52-2.42,1.08-5.03,1.62-7.81,1.62s-5.39-.54-7.84-1.62c-2.42-1.08-4.56-2.58-6.42-4.52-1.85-1.95-3.3-4.21-4.34-6.79-1.04-2.57-1.56-5.37-1.56-8.38Z"
></path>
</g>
</g>
</svg>
</div>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 318.06 297.93"
style="width: 8rem"
>
<defs>
<style>
.cls-1 {
fill: #2e3524;
}
</style>
</defs>
<g id="Camada_1-2" data-name="Camada_1">
<g>
<path
class="cls-1"
d="M21.35,192.47l3.61,11.1,8.32-2.71-4.22-12.97,3.75-1.22,5.76,17.67-27.03,8.81-5.71-17.54,3.79-1.24,4.19,12.84,7.46-2.44-3.61-11.1,3.71-1.21h-.01Z"
/>
<path
class="cls-1"
d="M21.88,186.71c-3.21.5-5.91.15-8.1-1.05-2.17-1.19-3.46-3.05-3.85-5.55-.34-2.21.15-4.12,1.49-5.72l-10.7,1.65-.72-4.69,29.64-4.56.65,4.25-2.12.56c1.91,1.14,3.05,2.89,3.41,5.22.37,2.45-.3,4.57-2.04,6.38-1.74,1.81-4.3,2.99-7.67,3.5h.01ZM21.55,181.96c2.12-.32,3.71-.99,4.77-1.99s1.49-2.25,1.25-3.76c-.3-1.91-1.36-3.19-3.21-3.81l-8.91,1.37c-1.54,1.12-2.16,2.64-1.86,4.56.24,1.51,1.01,2.6,2.35,3.24,1.32.64,3.2.77,5.62.4h-.01Z"
/>
<path
class="cls-1"
d="M27.39,150.1c1.62,1.4,2.44,3.39,2.42,5.95-.01,2.29-.7,4.02-2.05,5.19-1.35,1.17-3.3,1.75-5.83,1.72l-13.68-.1.04-4.75,13.63.1c2.69.02,4.02-1.09,4.05-3.31.01-2.3-.8-3.86-2.45-4.69l-15.17-.1.04-4.75,21.13.15-.04,4.47-2.07.1h-.01Z"
/>
<path
class="cls-1"
d="M27.9,132.58c.19-1.17,0-2.2-.55-3.07-.55-.87-1.32-1.42-2.32-1.64l.71-4.41c1.29.26,2.44.86,3.44,1.8,1,.94,1.72,2.11,2.17,3.51.45,1.4.55,2.86.3,4.36-.47,2.91-1.79,5.07-3.96,6.48-2.16,1.4-4.91,1.84-8.23,1.3l-.47-.07c-3.17-.51-5.57-1.77-7.21-3.77-1.64-2-2.21-4.47-1.74-7.4.4-2.47,1.45-4.39,3.15-5.71,1.7-1.32,3.72-1.84,6.1-1.54l-.71,4.41c-1.2-.14-2.25.11-3.15.75-.89.64-1.44,1.55-1.62,2.74-.25,1.51.11,2.77,1.09,3.77.96,1,2.56,1.69,4.8,2.06l.75.12c2.26.36,4.01.22,5.26-.4,1.24-.64,1.99-1.72,2.24-3.27l-.02-.02Z"
/>
<path
class="cls-1"
d="M37.16,110.07c-.45.07-1.12.05-2.05-.09,1.04,1.92,1.22,3.92.55,6-.65,2.02-1.75,3.49-3.31,4.39s-3.2,1.09-4.89.54c-2.15-.69-3.54-2.01-4.17-3.97s-.47-4.42.47-7.38l.89-2.77-1.32-.42c-1.04-.34-1.96-.31-2.77.07-.81.39-1.41,1.16-1.79,2.36-.34,1.02-.35,1.95-.04,2.77.3.82.85,1.36,1.65,1.61l-1.45,4.51c-1.1-.35-2.01-1.05-2.74-2.09s-1.16-2.27-1.3-3.71c-.14-1.44.04-2.92.54-4.47.76-2.36,1.95-4.05,3.59-5.06,1.64-1.02,3.52-1.2,5.68-.55l9.07,2.91c1.81.59,3.34.79,4.57.62l.31.1-1.49,4.61v.02ZM32.31,113.99c.29-.89.34-1.8.16-2.72-.19-.92-.57-1.71-1.17-2.34l-3.79-1.21-.79,2.44c-.54,1.67-.65,3.02-.34,4.05s1,1.71,2.06,2.06c.87.27,1.65.21,2.35-.2.7-.41,1.2-1.1,1.51-2.06h0Z"
/>
<path
class="cls-1"
d="M40.76,93.24c.52-1.06.66-2.09.39-3.1-.26-1-.85-1.76-1.74-2.26l2-4c1.15.64,2.06,1.55,2.75,2.75.67,1.2,1.01,2.52,1.02,4,0,1.47-.32,2.89-1.01,4.25-1.31,2.65-3.22,4.31-5.7,5.01-2.49.7-5.24.29-8.26-1.21l-.44-.21c-2.87-1.44-4.8-3.35-5.76-5.75s-.79-4.92.54-7.58c1.12-2.25,2.69-3.75,4.71-4.51,2.01-.76,4.11-.65,6.27.35l-2,4c-1.11-.49-2.19-.56-3.22-.22-1.04.34-1.84,1.05-2.37,2.12-.69,1.37-.71,2.69-.09,3.92s1.95,2.37,3.97,3.4l.67.34c2.05,1.02,3.76,1.41,5.14,1.17,1.37-.24,2.41-1.05,3.11-2.45h.01ZM44.4,93.61l.85.66c1.19-1.59,2.62-1.96,4.32-1.11,1.22.61,1.92,1.59,2.11,2.94.19,1.35-.16,2.91-1.07,4.72l-2.45-1.07c.39-.77.54-1.42.47-1.97-.06-.55-.35-.95-.84-1.19-.52-.26-.99-.24-1.4.07-.41.31-.87.92-1.4,1.82l-2.15-1.77,1.54-3.1h.01Z"
/>
<path
class="cls-1"
d="M34.01,54.85c1.17.82,1.91,1.85,2.21,3.07.3,1.22.09,2.35-.64,3.36-.29.41-.59.74-.9.97-.3.24-.81.52-1.52.86s-1.19.59-1.41.77c-.22.19-.44.41-.64.69-.29.4-.37.85-.26,1.35.1.5.4.91.89,1.26l-1.81,2.29c-1.17-.82-1.91-1.86-2.24-3.1-.32-1.24-.12-2.36.59-3.37.42-.61,1.19-1.17,2.26-1.69,1.09-.51,1.82-1.05,2.21-1.61.29-.4.37-.86.27-1.35-.1-.5-.4-.92-.9-1.29l1.87-2.24h.01ZM56.32,74.49c-.45-.06-1.1-.29-1.92-.69.41,2.14,0,4.11-1.27,5.9-1.22,1.74-2.71,2.8-4.47,3.2s-3.37.09-4.82-.95c-1.84-1.3-2.77-2.97-2.79-5.04-.02-2.06.87-4.36,2.66-6.91l1.67-2.37-1.14-.8c-.89-.64-1.79-.89-2.67-.76-.89.12-1.7.69-2.41,1.71-.62.89-.91,1.76-.87,2.64.04.87.4,1.55,1.09,2.04l-2.74,3.87c-.95-.66-1.61-1.61-1.99-2.81-.37-1.21-.42-2.52-.12-3.92.29-1.41.91-2.77,1.85-4.11,1.42-2.02,3.07-3.27,4.94-3.76,1.86-.49,3.72-.09,5.58,1.17l7.78,5.5c1.55,1.1,2.95,1.75,4.19,1.97l.27.19-2.79,3.96-.02-.02ZM50.53,76.78c.54-.76.86-1.61.97-2.55.1-.94-.04-1.8-.42-2.59l-3.26-2.3-1.47,2.09c-1.01,1.44-1.52,2.69-1.54,3.77,0,1.07.44,1.94,1.35,2.59.75.52,1.51.7,2.3.51.79-.19,1.47-.69,2.06-1.51h.01Z"
/>
<path
class="cls-1"
d="M52.8,61.26c-1.49-1.45-2.52-3.05-3.14-4.79-.61-1.75-.72-3.46-.32-5.15.4-1.69,1.27-3.21,2.62-4.6,1.99-2.04,4.26-3.05,6.82-3.04,2.55.01,4.97,1.02,7.23,3.05l.81.76c1.49,1.46,2.54,3.05,3.15,4.76.61,1.72.72,3.42.32,5.12s-1.29,3.24-2.65,4.64c-2.09,2.14-4.46,3.15-7.13,3.04s-5.19-1.31-7.55-3.62l-.16-.16h-.01ZM56.41,58.16c1.55,1.52,3.09,2.39,4.6,2.6,1.51.21,2.82-.25,3.94-1.39,1.11-1.15,1.54-2.47,1.27-3.99-.27-1.51-1.26-3.11-2.99-4.8-1.52-1.49-3.06-2.35-4.6-2.56-1.54-.21-2.85.24-3.95,1.36-1.09,1.11-1.5,2.41-1.26,3.92s1.24,3.12,2.99,4.84h0Z"
/>
<path
class="cls-1"
d="M77.44,38.38c-1.76-2.8-2.52-5.42-2.27-7.86.25-2.45,1.46-4.35,3.64-5.72,2-1.25,4.01-1.55,6.06-.89l-.89-1.91,3.57-2.25,15.57,24.75-4.01,2.52-5.29-8.4c-.39,1.96-1.52,3.54-3.4,4.72-2.14,1.35-4.37,1.59-6.71.72s-4.44-2.76-6.28-5.7h.01ZM81.68,36.2c1.15,1.82,2.41,3.04,3.8,3.62,1.37.59,2.7.47,3.99-.32,1.59-1,2.34-2.41,2.26-4.26l-5.11-8.13c-1.59-.81-3.16-.72-4.74.25-1.27.8-1.96,1.95-2.05,3.44s.54,3.29,1.86,5.4h-.01Z"
/>
<path
class="cls-1"
d="M113.39,28.7c-.64,2.06-2.12,3.59-4.49,4.6-2.11.9-3.96.97-5.57.21-1.61-.76-2.92-2.31-3.92-4.65l-5.4-12.57,4.36-1.87,5.37,12.52c1.06,2.46,2.61,3.26,4.66,2.37,2.11-.91,3.22-2.29,3.31-4.12l-5.98-13.93,4.36-1.87,8.33,19.42-4.11,1.76-.92-1.86Z"
/>
<path
class="cls-1"
d="M131.14,25.68c-2.91.74-5.51.42-7.78-.95-2.27-1.37-3.8-3.59-4.59-6.63l-.15-.56c-.52-2.05-.59-3.97-.21-5.77.37-1.8,1.17-3.34,2.36-4.6,1.2-1.26,2.67-2.11,4.42-2.56,2.79-.71,5.16-.36,7.13,1.02,1.97,1.4,3.37,3.71,4.2,6.97l.47,1.85-13.38,3.4c.58,1.65,1.47,2.85,2.71,3.59,1.24.72,2.61.91,4.11.52,2.11-.54,3.6-1.82,4.5-3.86l3.07,1.74c-.51,1.44-1.36,2.66-2.56,3.69-1.2,1.02-2.65,1.75-4.35,2.19l.04-.02ZM126.13,8.29c-1.26.32-2.16,1.02-2.72,2.1-.55,1.07-.74,2.44-.55,4.06l8.76-2.24-.09-.34c-.5-1.51-1.2-2.57-2.12-3.17-.91-.6-2.01-.75-3.29-.42h.01Z"
/>
<path
class="cls-1"
d="M150.25,10.96c-.06-3.27.65-5.91,2.15-7.9,1.5-1.99,3.52-3.01,6.08-3.06,2.41-.05,4.32.75,5.73,2.4l.17-2.14,4.27-.09.41,20.48c.05,2.77-.76,4.97-2.45,6.61-1.69,1.64-4,2.49-6.92,2.54-1.55.04-3.07-.26-4.56-.87-1.49-.61-2.62-1.44-3.41-2.46l2.19-2.9c1.49,1.7,3.3,2.54,5.44,2.49,1.57-.04,2.81-.49,3.7-1.35.89-.87,1.32-2.14,1.29-3.79l-.02-1.42c-1.34,1.55-3.15,2.35-5.42,2.4-2.47.05-4.51-.89-6.12-2.82-1.61-1.94-2.45-4.64-2.51-8.11h-.01ZM154.97,11.27c.05,2.12.51,3.79,1.4,4.97.89,1.2,2.1,1.79,3.64,1.75,1.91-.04,3.31-.89,4.21-2.55l-.19-9.3c-.92-1.59-2.35-2.35-4.26-2.31-1.56.04-2.76.67-3.6,1.92-.84,1.25-1.24,3.09-1.19,5.5h-.01Z"
/>
<path
class="cls-1"
d="M185.4,23.29c-.15-.42-.24-1.1-.27-2.02-1.71,1.35-3.65,1.87-5.81,1.59-2.1-.29-3.74-1.12-4.9-2.51-1.16-1.39-1.62-2.96-1.37-4.72.31-2.22,1.37-3.82,3.19-4.79,1.82-.96,4.27-1.24,7.36-.81l2.89.4.19-1.37c.15-1.09-.04-1.99-.55-2.72s-1.4-1.19-2.64-1.36c-1.07-.15-1.99,0-2.74.44-.75.44-1.2,1.07-1.31,1.9l-4.7-.65c.15-1.15.69-2.17,1.59-3.06s2.04-1.54,3.42-1.92c1.39-.39,2.89-.47,4.5-.25,2.45.34,4.32,1.22,5.61,2.65,1.29,1.42,1.8,3.26,1.52,5.5l-1.3,9.43c-.26,1.89-.2,3.42.17,4.61l-.05.32-4.8-.66v.02ZM180.71,19.19c.92.12,1.84.02,2.71-.31.89-.34,1.57-.86,2.1-1.56l.55-3.95-2.54-.35c-1.74-.24-3.09-.11-4.05.36-.96.49-1.51,1.29-1.67,2.39-.12.9.07,1.66.6,2.29s1.3,1,2.3,1.14Z"
/>
<path
class="cls-1"
d="M208.47,11.57c-.57-.26-1.19-.49-1.82-.65-2.1-.56-3.74-.15-4.9,1.27l-3.76,13.94-4.59-1.24,5.51-20.39,4.37,1.17-.5,2.31c1.59-1.47,3.36-1.95,5.32-1.41.65.17,1.17.41,1.55.7l-1.17,4.3h-.01Z"
/>
<path
class="cls-1"
d="M217.34,32.92c-.04-.45.04-1.12.24-2.04-1.99.89-4,.92-6.02.1-1.96-.8-3.35-2.01-4.12-3.64-.77-1.62-.85-3.26-.17-4.92.85-2.09,2.27-3.37,4.27-3.86,2-.49,4.45-.14,7.32,1.02l2.7,1.1.52-1.29c.41-1.01.46-1.94.14-2.77-.32-.84-1.06-1.5-2.21-1.96-1-.4-1.92-.49-2.76-.25s-1.42.75-1.74,1.52l-4.4-1.79c.44-1.07,1.2-1.94,2.29-2.57,1.09-.65,2.35-.99,3.8-1.01,1.44-.04,2.91.26,4.42.87,2.29.94,3.89,2.25,4.77,3.96.9,1.71.94,3.61.12,5.71l-3.59,8.82c-.71,1.76-1.04,3.26-.97,4.51l-.12.31-4.49-1.82h0ZM213.79,27.8c.87.35,1.77.47,2.71.36s1.74-.45,2.42-1l1.5-3.69-2.37-.96c-1.62-.66-2.96-.87-4.01-.64-1.05.24-1.79.87-2.21,1.91-.35.84-.34,1.62.02,2.36.36.72,1.01,1.29,1.95,1.66h-.01Z"
/>
<path
class="cls-1"
d="M239.28,20.36l-1.14,2.16c2.31-.81,4.55-.56,6.72.75,3.77,2.27,4.39,5.58,1.85,9.93l-7.21,11.96-4.06-2.45,7.06-11.72c.69-1.15.96-2.15.79-3s-.81-1.61-1.94-2.3c-1.64-.99-3.3-.99-5,.01l-7.76,12.87-4.06-2.45,10.91-18.09,3.82,2.31h.01Z"
/>
<path
class="cls-1"
d="M263.61,30.79l-3.19,4.02,2.92,2.31-2.19,2.76-2.92-2.31-7.32,9.25c-.5.64-.74,1.19-.71,1.66s.36.97,1,1.49c.42.34.9.64,1.42.87l-2.27,2.87c-1.04-.44-1.94-.96-2.72-1.59-2.86-2.26-3.04-4.97-.54-8.12l7.43-9.38-2.72-2.16,2.19-2.76,2.72,2.16,3.19-4.02,3.72,2.95h-.01Z"
/>
<path
class="cls-1"
d="M258.67,62.34c-2.11-2.15-3.14-4.55-3.1-7.21.05-2.66,1.19-5.09,3.44-7.3l.41-.41c1.51-1.47,3.14-2.51,4.89-3.09,1.75-.57,3.47-.67,5.16-.26,1.69.4,3.17,1.25,4.44,2.54,2.01,2.05,2.91,4.29,2.7,6.68-.21,2.41-1.51,4.79-3.91,7.13l-1.36,1.34-9.67-9.85c-1.15,1.32-1.72,2.7-1.72,4.15s.52,2.71,1.61,3.82c1.52,1.55,3.39,2.2,5.6,1.95l.05,3.54c-1.5.27-2.99.16-4.47-.36-1.49-.52-2.85-1.41-4.07-2.66h.02ZM271.19,49.26c-.91-.92-1.97-1.36-3.19-1.29-1.21.06-2.47.59-3.79,1.57l6.33,6.45.25-.25c1.06-1.19,1.62-2.32,1.69-3.42s-.36-2.12-1.29-3.06h-.01Z"
/>
<path
class="cls-1"
d="M283.39,84.8c.72-.45,1.09-1.09,1.09-1.91s-.27-2.01-.84-3.56c-.56-1.55-.91-2.92-1.04-4.11-.27-2.61.5-4.5,2.35-5.65,1.55-.96,3.25-1.11,5.1-.46,1.85.66,3.4,1.99,4.65,4,1.34,2.14,1.89,4.21,1.64,6.18-.24,1.97-1.2,3.5-2.9,4.55l-2.51-4.02c.77-.49,1.24-1.17,1.4-2.06.15-.9-.06-1.81-.65-2.76-.55-.89-1.2-1.47-1.96-1.79-.75-.3-1.47-.24-2.16.19-.62.39-.94.94-.96,1.67-.01.74.29,2,.92,3.8.64,1.8.99,3.3,1.07,4.5.07,1.2-.07,2.22-.49,3.09-.4.86-1.09,1.59-2.05,2.19-1.61,1-3.34,1.15-5.17.44-1.84-.71-3.42-2.16-4.77-4.32-.91-1.46-1.46-2.94-1.65-4.42-.19-1.47,0-2.82.51-4.05s1.32-2.16,2.4-2.82l2.44,3.91c-.91.65-1.42,1.46-1.52,2.45-.1.99.19,2.02.87,3.11.66,1.06,1.36,1.74,2.11,2.05.75.31,1.45.25,2.1-.16h.02Z"
/>
<path
class="cls-1"
d="M289.39,105.55c-2.05-.64-3.59-2.14-4.6-4.49-.9-2.11-.96-3.96-.2-5.57.77-1.61,2.32-2.91,4.65-3.91l12.58-5.39,1.86,4.36-12.53,5.36c-2.46,1.06-3.26,2.61-2.39,4.65.91,2.12,2.28,3.22,4.11,3.31l13.94-5.97,1.86,4.36-19.42,8.32-1.76-4.11,1.85-.92h.04Z"
/>
<path
class="cls-1"
d="M293.58,126.44c.34-.3.94-.64,1.77-1.01-1.9-1.07-3.11-2.67-3.65-4.8-.52-2.06-.36-3.89.49-5.47.85-1.6,2.14-2.61,3.86-3.05,2.19-.55,4.06-.16,5.63,1.16,1.57,1.32,2.75,3.5,3.51,6.51l.71,2.82,1.35-.34c1.06-.27,1.84-.77,2.32-1.54s.57-1.74.27-2.95c-.26-1.05-.75-1.84-1.44-2.37s-1.44-.7-2.25-.5l-1.16-4.6c1.12-.29,2.27-.17,3.44.32s2.19,1.31,3.06,2.46c.87,1.15,1.51,2.5,1.91,4.07.6,2.4.49,4.46-.36,6.2-.85,1.72-2.35,2.89-4.52,3.47l-9.23,2.34c-1.84.46-3.25,1.1-4.21,1.89l-.32.07-1.19-4.7ZM295.62,120.55c.22.91.66,1.71,1.31,2.4s1.39,1.14,2.24,1.36l3.86-.97-.62-2.49c-.44-1.7-1.05-2.91-1.86-3.61-.81-.71-1.76-.92-2.85-.65-.89.22-1.51.7-1.89,1.41-.37.72-.44,1.57-.19,2.55Z"
/>
<path
class="cls-1"
d="M302.38,155.39c.85-.02,1.49-.39,1.91-1.09s.79-1.87,1.09-3.5c.3-1.62.69-2.97,1.19-4.07,1.09-2.4,2.71-3.62,4.89-3.67,1.82-.05,3.36.69,4.62,2.19,1.26,1.51,1.92,3.44,1.99,5.81.06,2.52-.51,4.57-1.72,6.16-1.21,1.59-2.81,2.4-4.8,2.45l-.11-4.75c.91-.02,1.66-.37,2.25-1.07.59-.69.87-1.6.84-2.71-.02-1.04-.29-1.89-.79-2.52-.5-.65-1.15-.96-1.96-.94-.72.02-1.29.34-1.67.96s-.76,1.87-1.12,3.74c-.36,1.87-.81,3.34-1.35,4.42-.54,1.07-1.2,1.87-1.97,2.41-.77.54-1.74.81-2.87.84-1.9.05-3.46-.7-4.67-2.25-1.21-1.55-1.86-3.6-1.92-6.15-.05-1.72.24-3.27.82-4.65s1.42-2.45,2.5-3.22c1.07-.79,2.24-1.19,3.51-1.22l.11,4.61c-1.11.09-1.97.54-2.55,1.34-.59.8-.86,1.84-.84,3.12.04,1.25.29,2.19.79,2.84.5.65,1.12.95,1.89.92h-.02Z"
/>
<path
class="cls-1"
d="M295.03,172.54c.37-2.99,1.61-5.29,3.72-6.9,2.11-1.61,4.74-2.24,7.86-1.84l.57.07c2.09.26,3.91.9,5.46,1.91s2.69,2.31,3.42,3.89c.74,1.57.99,3.26.76,5.05-.36,2.85-1.55,4.95-3.56,6.27-2.01,1.32-4.69,1.79-8.02,1.36l-1.9-.24,1.72-13.69c-1.75-.07-3.19.32-4.32,1.21-1.14.89-1.8,2.1-1.99,3.64-.27,2.16.37,4.02,1.95,5.6l-2.74,2.24c-1.15-1-1.97-2.24-2.49-3.72-.51-1.49-.66-3.1-.45-4.85h-.01ZM313.06,174.24c.16-1.29-.16-2.39-.96-3.3-.8-.91-2-1.57-3.59-2l-1.12,8.97.35.05c1.59.1,2.84-.17,3.74-.81.9-.64,1.42-1.6,1.6-2.91h-.01Z"
/>
<path
class="cls-1"
d="M304.31,185.74c3.15.91,5.45,2.37,6.91,4.4,1.45,2.02,1.82,4.26,1.11,6.71-.66,2.31-2,3.9-4,4.76l1.99.8-1.19,4.11-19.68-5.67c-2.66-.77-4.52-2.2-5.58-4.3-1.06-2.1-1.19-4.56-.37-7.37.42-1.49,1.16-2.85,2.19-4.1,1.02-1.24,2.15-2.09,3.36-2.54l2.11,2.95c-2.06.92-3.4,2.41-3.99,4.46-.44,1.51-.37,2.82.2,3.94.57,1.11,1.64,1.9,3.24,2.35l1.37.4c-1.09-1.74-1.31-3.7-.67-5.88.69-2.37,2.19-4.05,4.51-5.01,2.32-.96,5.15-.96,8.5,0ZM302.61,190.17c-2.04-.59-3.76-.64-5.17-.14-1.41.5-2.32,1.47-2.75,2.95-.52,1.84-.14,3.42,1.19,4.77l8.93,2.57c1.79-.42,2.95-1.55,3.47-3.39.44-1.5.17-2.84-.77-4.01s-2.57-2.1-4.9-2.76Z"
/>
<path
class="cls-1"
d="M283.9,216.72c-.89-1.96-.79-4.1.31-6.42.97-2.07,2.32-3.35,4.04-3.85,1.71-.49,3.72-.2,6.01.89l12.38,5.83-2.02,4.29-12.33-5.81c-2.42-1.15-4.11-.71-5.06,1.3-.99,2.09-.9,3.85.25,5.27l13.72,6.47-2.02,4.29-19.1-9.01,1.91-4.05,1.92.77v.02Z"
/>
<path
class="cls-1"
d="M286.1,241.67c.42-.47.81-.99,1.17-1.54,1.17-1.82,1.26-3.51.26-5.05l-12.16-7.8,2.56-4,17.78,11.39-2.45,3.81-2.05-1.17c.92,1.95.84,3.79-.25,5.5-.36.57-.75.99-1.14,1.26l-3.74-2.42h0Z"
/>
<path
class="cls-1"
d="M263.04,243.68c.45.1,1.06.37,1.86.84-.25-2.16.32-4.1,1.72-5.77,1.36-1.62,2.92-2.57,4.72-2.84,1.79-.26,3.36.19,4.74,1.32,1.74,1.44,2.52,3.19,2.39,5.25-.15,2.06-1.21,4.29-3.2,6.67l-1.86,2.24,1.06.89c.84.7,1.71,1.02,2.61.97.9-.05,1.74-.55,2.54-1.51.69-.82,1.05-1.69,1.07-2.56.02-.87-.27-1.59-.92-2.11l3.04-3.65c.89.74,1.47,1.72,1.76,2.96.29,1.24.22,2.55-.17,3.92-.4,1.39-1.12,2.7-2.16,3.95-1.59,1.9-3.32,3.02-5.21,3.36-1.89.34-3.72-.2-5.47-1.61l-7.32-6.1c-1.46-1.21-2.8-1.97-4.01-2.3l-.25-.21,3.1-3.72h-.02ZM269.02,241.86c-.6.72-.99,1.55-1.16,2.47-.17.92-.11,1.8.21,2.61l3.06,2.55,1.64-1.96c1.12-1.35,1.74-2.56,1.82-3.64.1-1.07-.29-1.96-1.15-2.69-.7-.57-1.45-.81-2.25-.69-.8.12-1.52.57-2.17,1.35h0Z"
/>
<path
class="cls-1"
d="M268.33,268.44l-1.71-1.75c.06,2.45-.85,4.51-2.76,6.18-3.31,2.9-6.65,2.49-10.03-1.25l-9.2-10.5,3.57-3.12,9.02,10.3c.89,1.01,1.75,1.56,2.61,1.66s1.79-.27,2.77-1.15c1.44-1.26,1.94-2.85,1.5-4.77l-9.91-11.31,3.57-3.12,13.92,15.88-3.36,2.95Z"
/>
<path
class="cls-1"
d="M233.17,268.63l-.37-1.01c-1.84.74-3.26.31-4.27-1.3-.72-1.16-.82-2.36-.29-3.6.54-1.25,1.66-2.41,3.37-3.49l1.54,2.19c-.72.46-1.2.94-1.44,1.45-.22.51-.2.99.1,1.45.31.5.72.71,1.24.66.51-.05,1.22-.32,2.14-.82l.91,2.62-2.92,1.84h0ZM236.09,270.86c-1,.62-1.65,1.45-1.94,2.45-.3,1-.19,1.94.3,2.82l-3.79,2.39c-.66-1.14-.96-2.4-.92-3.77.05-1.37.45-2.69,1.2-3.95.76-1.26,1.79-2.3,3.07-3.11,2.5-1.57,5-2.01,7.47-1.31,2.49.7,4.62,2.47,6.42,5.32l.26.41c1.71,2.72,2.35,5.36,1.92,7.91-.42,2.55-1.9,4.62-4.41,6.2-2.12,1.34-4.25,1.81-6.37,1.41-2.12-.4-3.85-1.59-5.17-3.56l3.79-2.39c.69,1,1.57,1.61,2.64,1.86,1.06.25,2.11.05,3.12-.59,1.3-.82,2.01-1.92,2.12-3.31.11-1.39-.42-3.04-1.62-4.96l-.41-.65c-1.22-1.94-2.47-3.16-3.77-3.67-1.3-.51-2.61-.35-3.94.47l.02.02Z"
/>
<path
class="cls-1"
d="M213.07,278.96c.35.29.79.81,1.3,1.59.75-2.05,2.11-3.52,4.11-4.41,1.94-.86,3.77-1.01,5.49-.44,1.71.56,2.94,1.66,3.66,3.29.91,2.06.85,3.97-.2,5.76-1.04,1.77-2.99,3.3-5.83,4.56l-2.66,1.17.56,1.26c.45,1,1.07,1.67,1.9,2.02.82.35,1.81.27,2.95-.22.99-.44,1.69-1.04,2.1-1.81s.45-1.54.11-2.3l4.34-1.92c.47,1.06.56,2.2.26,3.44-.29,1.24-.92,2.37-1.91,3.44-.97,1.06-2.21,1.91-3.7,2.57-2.26,1-4.31,1.22-6.16.69-1.85-.54-3.25-1.84-4.2-3.87l-3.86-8.71c-.77-1.74-1.62-3.01-2.57-3.84l-.14-.3,4.42-1.96h.04ZM219.21,279.98c-.86.37-1.57.95-2.14,1.7-.57.75-.89,1.56-.96,2.44l1.61,3.65,2.34-1.04c1.61-.71,2.69-1.52,3.25-2.45.56-.92.61-1.89.16-2.91-.36-.84-.94-1.37-1.71-1.62-.77-.25-1.62-.16-2.55.25h0Z"
/>
<path
class="cls-1"
d="M203.98,287.44c-.85.26-1.57.22-2.19-.11s-1.02-.86-1.25-1.59c-.22-.71-.17-1.36.15-1.97.31-.61.9-1.04,1.74-1.3.81-.25,1.52-.21,2.14.1s1.04.84,1.25,1.55c.22.72.17,1.39-.14,2.01s-.89,1.06-1.72,1.31h.02Z"
/>
<path
class="cls-1"
d="M181.38,144.05h-73.14v21.89h73.14v-21.89Z"
/>
<path
class="cls-1"
d="M196.01,184.39h-87.77v29.11h87.77v-29.11Z"
/>
<path
class="cls-1"
d="M196.01,96.56h-87.77v29.11h87.77v-29.11Z"
/>
<path
class="cls-1"
d="M217.96,198.87h-.01c-4.04,0-7.31,3.27-7.31,7.31h0c0,4.05,3.27,7.32,7.31,7.32h.01c4.04,0,7.31-3.27,7.31-7.31h0c0-4.05-3.27-7.32-7.31-7.32Z"
/>
</g>
</g>
</svg>
</div>
</div>
<p>Certificamos que</p>
<h1>{{ name }}</h1>
<p>
Portador(a) do CPF <strong>{{ cpf }}</strong>, concluiu o
curso de <strong>{{ ... }}</strong> com aproveitamento de
<strong>{{ score }}%.</strong>
</p>
<p>
Realizado entre
<strong>{{ started_at }}</strong> e
<strong>{{ completed_at }}.</strong>
</p>
<p>
Data de emissão do certificado:
<strong>{{ today }}.</strong>
</p>
<div class="signatures">
<div class="sign1">{{ name }}</div>
<div>
<a href="https://validar.iti.gov.br">
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/bandeira_validar_icp2_qualificada.png"
style="width: 250px"
/>
</a>
</div>
<div class="sign2">
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/tiago_sign.png"
alt="Tiago Maciel dos Santos"
/>
<p><strong>Tiago Maciel do Santos</strong></p>
<p>Responsável legal</p>
<p>EDUSEG LTDA</p>
<p>CNPJ 15.608.435/0001-90</p>
</div>
</div>
<div class="copy">
&copy; {{ year }} EDUSEG&reg; Todos os direitos reservados.
CNPJ 15.608.435/0001-90
</div>
</div>
</section>
<section id="back">
<div class="space-y-2.5">
<h1>Conteúdo programático ministrado</h1>
<ul>
<li>...</li>
</ul>
</div>
<div class="space-y-5">
<dd class="space-y-0.5">
<h2>Treinamento ministrado</h2>
<p>{{ ... }}</p>
</dd>
<dd class="space-y-0.5">
<h2>Carga horária</h2>
<p>{{ hours }} horas</p>
</dd>
<dd class="space-y-0.5">
<h2>Validade</h2>
<p>{{ expires_at }}</p>
</dd>
<dd class="space-y-0.5">
<h2>Instrutora e responsável técnico</h2>
<div>
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/francis_sign.png"
class="trainer"
/>
<p>
<strong>Francis Ricardo Baretta</strong>
</p>
<p>CPF 039.539.409-02</p>
<p>Eng. de Segurança no Trabalho Eng. Eletricista</p>
<p>CREA/SC 126693-0</p>
</div>
</dd>
</div>
</section>
</body>
</html>

View File

@@ -0,0 +1 @@
{"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest" }

View File

@@ -21,12 +21,12 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "boto3-stubs", extra = ["cognito-idp", "essential"] }, { name = "boto3-stubs", extra = ["essential"] },
{ name = "jsonlines" }, { name = "jsonlines" },
{ name = "psycopg2-binary" },
{ name = "pycouchdb" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-cov" }, { name = "pytest-cov" },
{ name = "python-multipart" },
{ name = "requests-toolbelt" },
{ name = "ruff" }, { name = "ruff" },
{ name = "sqlite-utils" }, { name = "sqlite-utils" },
{ name = "tqdm" }, { name = "tqdm" },
@@ -37,12 +37,12 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }]
[package.metadata.requires-dev] [package.metadata.requires-dev]
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 = "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", specifier = ">=8.3.4" },
{ name = "pytest-cov", specifier = ">=6.0.0" }, { 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 = "ruff", specifier = ">=0.9.1" },
{ name = "sqlite-utils", specifier = ">=3.38" }, { name = "sqlite-utils", specifier = ">=3.38" },
{ name = "tqdm", specifier = ">=4.67.1" }, { name = "tqdm", specifier = ">=4.67.1" },
@@ -166,9 +166,6 @@ wheels = [
] ]
[package.optional-dependencies] [package.optional-dependencies]
cognito-idp = [
{ name = "mypy-boto3-cognito-idp" },
]
essential = [ essential = [
{ name = "mypy-boto3-cloudformation" }, { name = "mypy-boto3-cloudformation" },
{ name = "mypy-boto3-dynamodb" }, { 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" }, { 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]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.4.3" 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" }, { 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]] [[package]]
name = "jsonlines" name = "jsonlines"
version = "4.0.0" version = "4.0.0"
@@ -603,6 +603,7 @@ dependencies = [
{ name = "dictdiffer" }, { name = "dictdiffer" },
{ name = "ftfy" }, { name = "ftfy" },
{ name = "glom" }, { name = "glom" },
{ name = "joserfc" },
{ name = "meilisearch" }, { name = "meilisearch" },
{ name = "orjson" }, { name = "orjson" },
{ name = "passlib" }, { name = "passlib" },
@@ -610,7 +611,6 @@ dependencies = [
{ name = "pycpfcnpj" }, { name = "pycpfcnpj" },
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "pyjwt" },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "smart-open", extra = ["s3"] }, { name = "smart-open", extra = ["s3"] },
@@ -626,6 +626,7 @@ requires-dist = [
{ name = "dictdiffer", specifier = ">=0.9.0" }, { name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "ftfy", specifier = ">=6.3.1" }, { name = "ftfy", specifier = ">=6.3.1" },
{ name = "glom", specifier = ">=24.11.0" }, { name = "glom", specifier = ">=24.11.0" },
{ name = "joserfc", specifier = ">=1.2.2" },
{ name = "meilisearch", specifier = ">=0.34.0" }, { name = "meilisearch", specifier = ">=0.34.0" },
{ name = "orjson", specifier = ">=3.10.15" }, { name = "orjson", specifier = ">=3.10.15" },
{ name = "passlib", specifier = ">=1.7.4" }, { name = "passlib", specifier = ">=1.7.4" },
@@ -633,7 +634,6 @@ requires-dist = [
{ name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pycpfcnpj", specifier = ">=1.8" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
{ name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "pyjwt", specifier = ">=2.10.1" },
{ name = "pytz", specifier = ">=2025.1" }, { name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" }, { name = "requests", specifier = ">=2.32.3" },
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
@@ -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" }, { 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]] [[package]]
name = "mypy-boto3-dynamodb" name = "mypy-boto3-dynamodb"
version = "1.40.20" 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" }, { 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]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.23" 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" }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
] ]
[[package]]
name = "pyjwt"
version = "2.10.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.4.2" 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" }, { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
] ]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"
@@ -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" }, { 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]] [[package]]
name = "ruff" name = "ruff"
version = "0.13.1" version = "0.13.1"

View File

@@ -1,19 +1,19 @@
import os import os
from typing import TYPE_CHECKING
import boto3 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') return boto3.client('dynamodb')
dockerhost = 'host.docker.internal' return boto3.client('dynamodb', endpoint_url='http://127.0.0.1:8000')
localhost = '127.0.0.1'
host = dockerhost if running_sam_local else localhost
return boto3.client('dynamodb', endpoint_url=f'http://{host}:8000')
dynamodb_client = get_dynamodb_client() dynamodb_client: DynamoDBClient = get_dynamodb_client()

View File

@@ -157,7 +157,6 @@ class AuthorizationServer(oauth2.AuthorizationServer):
body, body,
headers, headers,
): ):
logger.debug('handle_response', status=status, body=body)
return Response( return Response(
status_code=status, status_code=status,
body=body, body=body,

View File

@@ -1,43 +1,43 @@
import { isbot } from "isbot"; import { isbot } from 'isbot'
import { renderToReadableStream } from "react-dom/server"; import { renderToReadableStream } from 'react-dom/server'
import type { AppLoadContext, EntryContext } from "react-router"; import type { AppLoadContext, EntryContext } from 'react-router'
import { ServerRouter } from "react-router"; import { ServerRouter } from 'react-router'
export default async function handleRequest( export default async function handleRequest(
request: Request, request: Request,
responseStatusCode: number, responseStatusCode: number,
responseHeaders: Headers, responseHeaders: Headers,
routerContext: EntryContext, routerContext: EntryContext,
_loadContext: AppLoadContext, _loadContext: AppLoadContext
) { ) {
let shellRendered = false; let shellRendered = false
const userAgent = request.headers.get("user-agent"); const userAgent = request.headers.get('user-agent')
const body = await renderToReadableStream( const body = await renderToReadableStream(
<ServerRouter context={routerContext} url={request.url} />, <ServerRouter context={routerContext} url={request.url} />,
{ {
onError(error: unknown) { onError(error: unknown) {
responseStatusCode = 500; responseStatusCode = 500
// Log streaming rendering errors from inside the shell. Don't log // Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll // errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest. // reject and get logged in handleDocumentRequest.
if (shellRendered) { 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 // 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 // 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) { 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, { return new Response(body, {
headers: responseHeaders, headers: responseHeaders,
status: responseStatusCode, status: responseStatusCode
}); })
} }

View File

@@ -117,7 +117,7 @@ export default function Index({}: Route.ComponentProps) {
<div className="w-full max-w-xs grid gap-6"> <div className="w-full max-w-xs grid gap-6">
<div className="flex justify-center"> <div className="flex justify-center">
<div className="border border-white/15 bg-white/5 px-2.5 py-3 rounded-xl"> <div className="border border-white/15 bg-white/5 px-2.5 py-3 rounded-xl">
<img src={logo} alt="React Router" className="block size-12" /> <img src={logo} alt="EDUSEG®" className="block size-12" />
</div> </div>
</div> </div>

View File

@@ -4,9 +4,7 @@ version = "0.1.0"
description = "Add your description here" description = "Add your description here"
readme = "" readme = ""
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = ["layercake"]
"layercake",
]
[dependency-groups] [dependency-groups]
dev = [ dev = [
@@ -14,6 +12,7 @@ dev = [
"pytest>=8.4.1", "pytest>=8.4.1",
"ruff>=0.12.1", "ruff>=0.12.1",
"pytest-cov>=6.2.1", "pytest-cov>=6.2.1",
"boto3-stubs[essential]>=1.40.44",
] ]

View File

@@ -1,5 +1,4 @@
import json import json
import pprint
from base64 import b64encode from base64 import b64encode
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
from urllib.parse import urlencode from urllib.parse import urlencode
@@ -44,7 +43,7 @@ def token(
return json.loads(r['body']) return json.loads(r['body'])
def test_token( def test_revoke(
app, app,
token, token,
seeds, seeds,

View File

@@ -7,6 +7,10 @@ from layercake.dynamodb import DynamoDBPersistenceLayer
from ..conftest import HttpApiProxy, LambdaContext 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( def test_token(
app, app,
@@ -91,22 +95,19 @@ def test_refresh_token(
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext, 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( r = app.lambda_handler(
http_api_proxy( http_api_proxy(
raw_path='/token', raw_path='/token',
method=HTTPMethod.POST, method=HTTPMethod.POST,
headers={ headers={
'Authorization': f'Basic {auth}', 'Authorization': f'Basic {AUTH}',
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
}, },
body=urlencode( body=urlencode(
{ {
'grant_type': 'refresh_token', 'grant_type': 'refresh_token',
'refresh_token': 'CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy', 'refresh_token': 'CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy',
'client_id': client_id, 'client_id': CLIENT_ID,
} }
), ),
), ),

View File

@@ -15,7 +15,7 @@
// User data // 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": "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": "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} {"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}

View File

@@ -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" }, { 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]] [[package]]
name = "botocore" name = "botocore"
version = "1.38.46" 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" }, { 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]] [[package]]
name = "camel-converter" name = "camel-converter"
version = "4.0.1" version = "4.0.1"
@@ -387,6 +423,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "boto3-stubs", extra = ["essential"] },
{ name = "jsonlines" }, { name = "jsonlines" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-cov" }, { name = "pytest-cov" },
@@ -398,6 +435,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "boto3-stubs", extras = ["essential"], specifier = ">=1.40.44" },
{ name = "jsonlines", specifier = ">=4.0.0" }, { name = "jsonlines", specifier = ">=4.0.0" },
{ name = "pytest", specifier = ">=8.4.1" }, { name = "pytest", specifier = ">=8.4.1" },
{ name = "pytest-cov", specifier = ">=6.2.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" }, { 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]] [[package]]
name = "orjson" name = "orjson"
version = "3.10.18" 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" }, { 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.14.0" version = "4.14.0"

View File

@@ -25,8 +25,8 @@ dependencies = [
"unidecode>=1.4.0", "unidecode>=1.4.0",
"authlib>=1.6.1", "authlib>=1.6.1",
"passlib>=1.7.4", "passlib>=1.7.4",
"pyjwt>=2.10.1",
"psycopg[binary]>=3.2.9", "psycopg[binary]>=3.2.9",
"joserfc>=1.2.2",
] ]
[dependency-groups] [dependency-groups]

15
layercake/uv.lock generated
View File

@@ -675,7 +675,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.9.13" version = "0.9.14"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },
@@ -684,6 +684,7 @@ dependencies = [
{ name = "dictdiffer" }, { name = "dictdiffer" },
{ name = "ftfy" }, { name = "ftfy" },
{ name = "glom" }, { name = "glom" },
{ name = "joserfc" },
{ name = "meilisearch" }, { name = "meilisearch" },
{ name = "orjson" }, { name = "orjson" },
{ name = "passlib" }, { name = "passlib" },
@@ -691,7 +692,6 @@ dependencies = [
{ name = "pycpfcnpj" }, { name = "pycpfcnpj" },
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "pyjwt" },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "smart-open", extra = ["s3"] }, { name = "smart-open", extra = ["s3"] },
@@ -718,6 +718,7 @@ requires-dist = [
{ name = "dictdiffer", specifier = ">=0.9.0" }, { name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "ftfy", specifier = ">=6.3.1" }, { name = "ftfy", specifier = ">=6.3.1" },
{ name = "glom", specifier = ">=24.11.0" }, { name = "glom", specifier = ">=24.11.0" },
{ name = "joserfc", specifier = ">=1.2.2" },
{ name = "meilisearch", specifier = ">=0.34.0" }, { name = "meilisearch", specifier = ">=0.34.0" },
{ name = "orjson", specifier = ">=3.10.15" }, { name = "orjson", specifier = ">=3.10.15" },
{ name = "passlib", specifier = ">=1.7.4" }, { name = "passlib", specifier = ">=1.7.4" },
@@ -725,7 +726,6 @@ requires-dist = [
{ name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pycpfcnpj", specifier = ">=1.8" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
{ name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "pyjwt", specifier = ">=2.10.1" },
{ name = "pytz", specifier = ">=2025.1" }, { name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" }, { name = "requests", specifier = ">=2.32.3" },
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
@@ -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" }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
] ]
[[package]]
name = "pyjwt"
version = "2.10.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
]
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "3.2.3" version = "3.2.3"