update batch
This commit is contained in:
@@ -24,6 +24,7 @@ Example
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger, Tracer
|
from aws_lambda_powertools import Logger, Tracer
|
||||||
@@ -33,7 +34,6 @@ from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event i
|
|||||||
APIGatewayAuthorizerResponseV2,
|
APIGatewayAuthorizerResponseV2,
|
||||||
)
|
)
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from botocore.endpoint_provider import Enum
|
|
||||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
|
||||||
from layercake.funcs import pick
|
from layercake.funcs import pick
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import boto3
|
|||||||
|
|
||||||
|
|
||||||
def get_dynamodb_client():
|
def get_dynamodb_client():
|
||||||
sam_local = os.getenv('AWS_SAM_LOCAL')
|
running_sam_local = os.getenv('AWS_SAM_LOCAL')
|
||||||
|
|
||||||
if os.getenv('AWS_LAMBDA_FUNCTION_NAME') and not sam_local:
|
if os.getenv('AWS_LAMBDA_FUNCTION_NAME') and not running_sam_local:
|
||||||
return boto3.client('dynamodb')
|
return boto3.client('dynamodb')
|
||||||
|
|
||||||
url = 'host.docker.internal' if sam_local else 'localhost'
|
url = 'host.docker.internal' if running_sam_local else 'localhost'
|
||||||
return boto3.client('dynamodb', endpoint_url=f'http://{url}:8000')
|
return boto3.client('dynamodb', endpoint_url=f'http://{url}:8000')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Course(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Enrollment(BaseModel):
|
class Enrollment(BaseModel):
|
||||||
id: UUID4 = Field(default_factory=uuid4)
|
id: UUID4 | str = Field(default_factory=uuid4)
|
||||||
user: User
|
user: User
|
||||||
course: Course
|
course: Course
|
||||||
progress: int = Field(default=0, ge=0, le=100)
|
progress: int = Field(default=0, ge=0, le=100)
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ def cancel(id: str, payload: Cancel):
|
|||||||
set_status_as_canceled(
|
set_status_as_canceled(
|
||||||
id,
|
id,
|
||||||
lock_hash=payload.lock_hash,
|
lock_hash=payload.lock_hash,
|
||||||
author=user.model_dump(), # type: ignore
|
author={
|
||||||
|
'id': user.id,
|
||||||
|
'name': user.name,
|
||||||
|
},
|
||||||
course=payload.course, # type: ignore
|
course=payload.course, # type: ignore
|
||||||
vacancy_key=KeyPair.parse_obj(payload.vacancy),
|
vacancy_key=KeyPair.parse_obj(payload.vacancy),
|
||||||
persistence_layer=enrollment_layer,
|
persistence_layer=enrollment_layer,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from layercake.batch import BatchProcessor
|
from layercake.batch import BatchProcessor
|
||||||
@@ -8,13 +9,15 @@ from layercake.dynamodb import (
|
|||||||
)
|
)
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from api_gateway import JSONResponse
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import (
|
from config import (
|
||||||
ENROLLMENT_TABLE,
|
ENROLLMENT_TABLE,
|
||||||
USER_TABLE,
|
USER_TABLE,
|
||||||
)
|
)
|
||||||
from middlewares import Tenant, TenantMiddleware
|
from middlewares import Tenant, TenantMiddleware
|
||||||
from models import Course, User
|
from models import Course, Enrollment, User
|
||||||
|
from rules.enrollment import enroll
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@@ -28,6 +31,7 @@ processor = BatchProcessor()
|
|||||||
class Item(BaseModel):
|
class Item(BaseModel):
|
||||||
user: User
|
user: User
|
||||||
course: Course
|
course: Course
|
||||||
|
deduplication_window: dict = {}
|
||||||
schedule_date: datetime | None = None
|
schedule_date: datetime | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -49,16 +53,33 @@ def enroll_(payload: Payload):
|
|||||||
with processor(payload.items, handler, context):
|
with processor(payload.items, handler, context):
|
||||||
processor.process()
|
processor.process()
|
||||||
|
|
||||||
return {}
|
print(processor.exceptions)
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
HTTPStatus.OK,
|
||||||
|
{
|
||||||
|
'successes': processor.successes,
|
||||||
|
'failures': processor.failures,
|
||||||
|
'exceptions': [str(exc) for exc in processor.exceptions],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def handler(record: Item, context: dict):
|
def handler(record: Item, context: dict):
|
||||||
tenant: Tenant = context['tenant']
|
tenant: Tenant = context['tenant']
|
||||||
# enroll(
|
enrollment = Enrollment(
|
||||||
# enrollment=Enrollment(user=[])
|
user=record.user,
|
||||||
# tenant={
|
course=record.course,
|
||||||
# 'id': str(tenant.id),
|
)
|
||||||
# 'name': tenant.name,
|
|
||||||
# },
|
enroll(
|
||||||
# persistence_layer=enrollment_layer,
|
enrollment=enrollment,
|
||||||
# )
|
tenant={
|
||||||
|
'id': str(tenant.id),
|
||||||
|
'name': tenant.name,
|
||||||
|
},
|
||||||
|
deduplication_window=record.deduplication_window, # type: ignore
|
||||||
|
persistence_layer=enrollment_layer,
|
||||||
|
)
|
||||||
|
|
||||||
|
return enrollment
|
||||||
|
|||||||
@@ -125,6 +125,11 @@ def enroll(
|
|||||||
ttl_expiration = ttl(
|
ttl_expiration = ttl(
|
||||||
start_dt=now_ + timedelta(days=course.access_period - offset_days)
|
start_dt=now_ + timedelta(days=course.access_period - offset_days)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class DeduplicationConflictError(Exception):
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__('Enrollment already exists')
|
||||||
|
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': 'lock',
|
'id': 'lock',
|
||||||
@@ -134,6 +139,7 @@ def enroll(
|
|||||||
'ttl': ttl_expiration,
|
'ttl': ttl_expiration,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
|
exc_cls=DeduplicationConflictError,
|
||||||
)
|
)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from typing import TypedDict
|
|||||||
from aws_lambda_powertools.event_handler.exceptions import (
|
from aws_lambda_powertools.event_handler.exceptions import (
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
)
|
)
|
||||||
from botocore.tokens import timedelta
|
|
||||||
from layercake.dateutils import now, ttl
|
from layercake.dateutils import now, ttl
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
ComposeKey,
|
ComposeKey,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ dependencies = ["layercake"]
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
|
"boto3-stubs[essential]>=1.38.26",
|
||||||
"jsonlines>=4.0.0",
|
"jsonlines>=4.0.0",
|
||||||
"pytest>=8.3.4",
|
"pytest>=8.3.4",
|
||||||
"pytest-cov>=6.0.0",
|
"pytest-cov>=6.0.0",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from http import HTTPMethod, HTTPStatus
|
from http import HTTPMethod, HTTPStatus
|
||||||
|
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
@@ -36,6 +37,21 @@ def test_enroll(
|
|||||||
'id': '6d69a34a-cefd-40aa-a89b-dceb694c3e61',
|
'id': '6d69a34a-cefd-40aa-a89b-dceb694c3e61',
|
||||||
'name': 'pytest',
|
'name': 'pytest',
|
||||||
},
|
},
|
||||||
|
'deduplication_window': {
|
||||||
|
'offset_days': 60,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user': {
|
||||||
|
'id': '9a41e867-55e1-4573-bd27-7b5d1d1bcfde',
|
||||||
|
'name': 'Tiago Maciel',
|
||||||
|
'email': 'tiago@somosbeta.com.br',
|
||||||
|
'cpf': '08679004901',
|
||||||
|
},
|
||||||
|
'course': {
|
||||||
|
'id': '6d69a34a-cefd-40aa-a89b-dceb694c3e61',
|
||||||
|
'name': 'pytest',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -43,8 +59,8 @@ def test_enroll(
|
|||||||
lambda_context,
|
lambda_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
# assert r['statusCode'] == HTTPStatus.OK
|
||||||
print(r)
|
print(json.loads(r['body']))
|
||||||
|
|
||||||
|
|
||||||
def test_vacancies(
|
def test_vacancies(
|
||||||
|
|||||||
@@ -21,3 +21,4 @@
|
|||||||
{"id": {"S": "email"}, "sk": {"S": "sergio@somosbeta.com.br"}}
|
{"id": {"S": "email"}, "sk": {"S": "sergio@somosbeta.com.br"}}
|
||||||
{"id": {"S": "cpf"}, "sk": {"S": "07879819908"}}
|
{"id": {"S": "cpf"}, "sk": {"S": "07879819908"}}
|
||||||
{"id": {"S": "cpf"}, "sk": {"S": "08679004901"}}
|
{"id": {"S": "cpf"}, "sk": {"S": "08679004901"}}
|
||||||
|
{"id": {"S": "lock"}, "sk": {"S": "c2116a43f8f1aed659a10c83dab17ed3"}}
|
||||||
119
http-api/uv.lock
generated
119
http-api/uv.lock
generated
@@ -103,6 +103,30 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/6e/98/bac2404ff6183e1aaeebfefe6f345d63a1395b9a710be5ad24dcad9538ed/boto3-1.37.20-py3-none-any.whl", hash = "sha256:225dbc75d79816cb9b28cc74a63c9fa0f2d70530d603dacd82634f362f6679c1", size = 139561, upload-time = "2025-03-25T19:21:44.723Z" },
|
{ url = "https://files.pythonhosted.org/packages/6e/98/bac2404ff6183e1aaeebfefe6f345d63a1395b9a710be5ad24dcad9538ed/boto3-1.37.20-py3-none-any.whl", hash = "sha256:225dbc75d79816cb9b28cc74a63c9fa0f2d70530d603dacd82634f362f6679c1", size = 139561, upload-time = "2025-03-25T19:21:44.723Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boto3-stubs"
|
||||||
|
version = "1.38.26"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "botocore-stubs" },
|
||||||
|
{ name = "types-s3transfer" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1e/89/9e2658210ac11459405ed8c82f47be533a5d16ae3b8203c90564b7a738a0/boto3_stubs-1.38.26.tar.gz", hash = "sha256:492e59e42323de43018ffa6d00d3bb2b93d1fead042e76c6a68fd0a0c0fe3236", size = 99065, upload-time = "2025-05-29T19:47:49.383Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/51/881ac3c7ebeefdfe0e712b1f4d815261707a492187ea301506168cf6fc20/boto3_stubs-1.38.26-py3-none-any.whl", hash = "sha256:3022b2a8f6925c60c9ce68c5e090ff9fd2bad0c918300395a1c242681a67c11c", size = 68669, upload-time = "2025-05-29T19:47:42.352Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[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.37.20"
|
version = "1.37.20"
|
||||||
@@ -117,6 +141,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/2d/5b/f96cf58c37704b907ac2f9cc94e45ba0a2aa3b2062421aa8b8614f1d78de/botocore-1.37.20-py3-none-any.whl", hash = "sha256:c34f4f25fda7c4f726adf5a948590bd6bd7892c05278d31e344b5908e7b43301", size = 13432464, upload-time = "2025-03-25T19:21:28.115Z" },
|
{ url = "https://files.pythonhosted.org/packages/2d/5b/f96cf58c37704b907ac2f9cc94e45ba0a2aa3b2062421aa8b8614f1d78de/botocore-1.37.20-py3-none-any.whl", hash = "sha256:c34f4f25fda7c4f726adf5a948590bd6bd7892c05278d31e344b5908e7b43301", size = 13432464, upload-time = "2025-03-25T19:21:28.115Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "botocore-stubs"
|
||||||
|
version = "1.38.26"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "types-awscrt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/08/f2/fd2f5a8ef00bbcfe00c12b8c49e247510266929dff5578b6fec360967a21/botocore_stubs-1.38.26.tar.gz", hash = "sha256:3bbf7662fc97e28a50dc959752619cf57029194987268b4dc13df4e54767204c", size = 42315, upload-time = "2025-05-29T20:18:25.22Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/4a/f99ef1ff37620c0c23aa67e3d9de5fce0f98b38fd26e6d30438ee440c0fc/botocore_stubs-1.38.26-py3-none-any.whl", hash = "sha256:c86ac7d2c7e24ea50a866a9686a293dfe8b40281cc3465d79e2e0e48d35ad93b", size = 65628, upload-time = "2025-05-29T20:18:23.125Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -450,6 +486,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" },
|
||||||
@@ -462,6 +499,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }]
|
|||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
|
{ name = "boto3-stubs", extras = ["essential"], specifier = ">=1.38.26" },
|
||||||
{ name = "jsonlines", specifier = ">=4.0.0" },
|
{ name = "jsonlines", specifier = ">=4.0.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" },
|
||||||
@@ -584,6 +622,69 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e0/2f/264c07a3f488260ea36c78cbc201b76e6baf9ef92e0c7f78657a6a5e5f22/meilisearch-0.34.0-py3-none-any.whl", hash = "sha256:fae8ad2a15d12c27fa0a1fff2ae2e4e3e2e22b869950408d63c87e2c095a9f61", size = 24373, upload-time = "2025-02-18T05:50:32.73Z" },
|
{ url = "https://files.pythonhosted.org/packages/e0/2f/264c07a3f488260ea36c78cbc201b76e6baf9ef92e0c7f78657a6a5e5f22/meilisearch-0.34.0-py3-none-any.whl", hash = "sha256:fae8ad2a15d12c27fa0a1fff2ae2e4e3e2e22b869950408d63c87e2c095a9f61", size = 24373, upload-time = "2025-02-18T05:50:32.73Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-cloudformation"
|
||||||
|
version = "1.38.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/40/d5/35b9301c8b2fb870e58401d13fec36de2c83f2ddef48398b8c89c9a58995/mypy_boto3_cloudformation-1.38.0.tar.gz", hash = "sha256:563399166c07e91e0695fb1e58103a248b2bee0db5e2c3f07155776dd6311805", size = 57702, upload-time = "2025-04-22T21:19:31.221Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/2f/d20ad6e0113f982ea255fcb4ed94f70d0111757d7d03bfacebc2d9f60ba4/mypy_boto3_cloudformation-1.38.0-py3-none-any.whl", hash = "sha256:a1411aa5875b737492aaac5f7e8ce450f034c18f972eb608a9eba6fe35837f6a", size = 69607, upload-time = "2025-04-22T21:19:29.235Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-dynamodb"
|
||||||
|
version = "1.38.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6b/7f/72b68d275a80a42675c36249b80dd79ec5c7d9bd1f5cc93cdb572f866722/mypy_boto3_dynamodb-1.38.4.tar.gz", hash = "sha256:5cf3787631e312b3d75f89a6cbbbd4ad786a76f5d565af023febf03fbf23c0b5", size = 47461, upload-time = "2025-04-28T19:26:22.728Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/35/3d0ceabb0a9f3765f509cb9dce6ddfa939114682b1acc442f52a755e9bc8/mypy_boto3_dynamodb-1.38.4-py3-none-any.whl", hash = "sha256:6b29d89c649eeb1e894118bee002cb8b1304c78da735b1503aa08e46b0abfdec", size = 56395, upload-time = "2025-04-28T19:26:16.947Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-ec2"
|
||||||
|
version = "1.38.25"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d4/88/b9a99e010224b2ad9ea45b96f6689d9706841f2788e5ffe114cb0041e543/mypy_boto3_ec2-1.38.25.tar.gz", hash = "sha256:aed7d746c7c6af7e3f75424ad64829a7ce5b94dc871114a449c403ada22954cb", size = 400494, upload-time = "2025-05-28T19:42:21.197Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/22/f7caf22d014c7b0b62546a41b49030b73c162702870bfd1b1aaf48e2cc05/mypy_boto3_ec2-1.38.25-py3-none-any.whl", hash = "sha256:bad444d731669eab25fdcb7259901cb0db0fb26a4e1a79836a32aef6d674dbd0", size = 389882, upload-time = "2025-05-28T19:42:17.484Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-lambda"
|
||||||
|
version = "1.38.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8f/e3/0436071b28942788bdd22d6f91847654a7b1d167fb9d86c5779108e49ee9/mypy_boto3_lambda-1.38.0.tar.gz", hash = "sha256:ece7b3848c045e1be81c4f2b7482002c17ce7cb70de850661146103a8cb1a3fb", size = 41767, upload-time = "2025-04-22T21:27:54.666Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/09/602a39b39abd0d58d8b6bbee4c1552b64fadba2324676d7d45c3fa00fe7b/mypy_boto3_lambda-1.38.0-py3-none-any.whl", hash = "sha256:0dcb882826f61fd2751f6b98330b0e11085570654db85318aea018374ca88dc9", size = 48210, upload-time = "2025-04-22T21:27:52.034Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-rds"
|
||||||
|
version = "1.38.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/97/b7/5ac46bee6617c8f955f6c28b8698a954f537812d84d655e3c887557421f0/mypy_boto3_rds-1.38.20.tar.gz", hash = "sha256:c6aa70c0cc5bc59959fec434206fbf8200386b583ff1f7e372154eaa41eb52e9", size = 85121, upload-time = "2025-05-20T23:30:09.631Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/ff/1a82134124ef41c040a40076c83b111d760f57007ec7b16649891fef0473/mypy_boto3_rds-1.38.20-py3-none-any.whl", hash = "sha256:9f600c24e687780fed1c8dc6d244b17dd0889f34705ec40c66df15e1caa420f4", size = 91368, upload-time = "2025-05-20T23:30:05.508Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-s3"
|
||||||
|
version = "1.38.26"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/79/a5/366aec375b77cfe7820b7b3213318b147aefda6f12a035691541a5d557d1/mypy_boto3_s3-1.38.26.tar.gz", hash = "sha256:38a45dee5782d5c07ddea07ea50965c4d2ba7e77617c19f613b4c9f80f961b52", size = 73717, upload-time = "2025-05-29T19:43:03.468Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/fa/251b651c18341c7491909994bd459b12ad05e13059d65bfa65d3afabdf8d/mypy_boto3_s3-1.38.26-py3-none-any.whl", hash = "sha256:1129d64be1aee863e04f0c92ac8d315578f13ccae64fa199b20ad0950d2b9616", size = 80321, upload-time = "2025-05-29T19:42:59.199Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-sqs"
|
||||||
|
version = "1.38.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/a0/ef5c7bdb33af5d0a48029fed11401388fa68949c6c0f9b11b2e845f5fe0e/mypy_boto3_sqs-1.38.0.tar.gz", hash = "sha256:39aebc121a2fe20f962fd83b617fd916003605d6f6851fdf195337a0aa428fe1", size = 23541, upload-time = "2025-04-22T21:35:17.315Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/97/72fccc9aaa0e3c8f3f99b4edac580ede651808aefb47b0d2b52c18a3d16b/mypy_boto3_sqs-1.38.0-py3-none-any.whl", hash = "sha256:8e881c8492f6f51dcbe1cce9d9f05334f4b256b5843e227fa925e0f6e702b31d", size = 33669, upload-time = "2025-04-22T21:35:16.073Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.10.16"
|
version = "3.10.16"
|
||||||
@@ -938,6 +1039,24 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-awscrt"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/36/6c/583522cfb3c330e92e726af517a91c13247e555e021791a60f1b03c6ff16/types_awscrt-0.27.2.tar.gz", hash = "sha256:acd04f57119eb15626ab0ba9157fc24672421de56e7bd7b9f61681fedee44e91", size = 16304, upload-time = "2025-05-16T03:10:08.712Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/82/1ee2e5c9d28deac086ab3a6ff07c8bc393ef013a083f546c623699881715/types_awscrt-0.27.2-py3-none-any.whl", hash = "sha256:49a045f25bbd5ad2865f314512afced933aed35ddbafc252e2268efa8a787e4e", size = 37761, upload-time = "2025-05-16T03:10:07.466Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-s3transfer"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/42/c1/45038f259d6741c252801044e184fec4dbaeff939a58f6160d7c32bf4975/types_s3transfer-0.13.0.tar.gz", hash = "sha256:203dadcb9865c2f68fb44bc0440e1dc05b79197ba4a641c0976c26c9af75ef52", size = 14175, upload-time = "2025-05-28T02:16:07.614Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/5d/6bbe4bf6a79fb727945291aef88b5ecbdba857a603f1bbcf1a6be0d3f442/types_s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:79c8375cbf48a64bff7654c02df1ec4b20d74f8c5672fc13e382f593ca5565b3", size = 19588, upload-time = "2025-05-28T02:16:06.709Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.13.0"
|
version = "4.13.0"
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ class Status(Enum):
|
|||||||
|
|
||||||
class Result(NamedTuple):
|
class Result(NamedTuple):
|
||||||
status: Status
|
status: Status
|
||||||
cause: Any
|
input_record: Any
|
||||||
record: Any
|
output: Any | None = None
|
||||||
|
cause: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class BatchProcessor(AbstractContextManager):
|
class BatchProcessor(AbstractContextManager):
|
||||||
@@ -112,7 +113,12 @@ class BatchProcessor(AbstractContextManager):
|
|||||||
result = self.handler(record)
|
result = self.handler(record)
|
||||||
|
|
||||||
self.successes.append(record)
|
self.successes.append(record)
|
||||||
return Result(Status.SUCCESS, result, record)
|
|
||||||
|
return Result(
|
||||||
|
status=Status.SUCCESS,
|
||||||
|
output=result,
|
||||||
|
input_record=record,
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
exc_str = f'{type(exc).__name__}: {exc}'
|
exc_str = f'{type(exc).__name__}: {exc}'
|
||||||
logger.debug(f'Record processing exception: {exc_str}')
|
logger.debug(f'Record processing exception: {exc_str}')
|
||||||
@@ -120,4 +126,8 @@ class BatchProcessor(AbstractContextManager):
|
|||||||
self.exceptions.append(exc)
|
self.exceptions.append(exc)
|
||||||
self.failures.append(record)
|
self.failures.append(record)
|
||||||
|
|
||||||
return Result(Status.FAIL, exc_str, record)
|
return Result(
|
||||||
|
status=Status.FAIL,
|
||||||
|
input_record=record,
|
||||||
|
cause=exc,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
|
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ def test_batch():
|
|||||||
with processor(records=records, handler=record_handler) as p:
|
with processor(records=records, handler=record_handler) as p:
|
||||||
processed_messages = p.process()
|
processed_messages = p.process()
|
||||||
|
|
||||||
assert processed_messages == (
|
assert len(processed_messages) == 3
|
||||||
Result(Status.SUCCESS, True, True),
|
|
||||||
Result(Status.SUCCESS, True, True),
|
fail_record = processed_messages[2]
|
||||||
Result(Status.FAIL, 'ValueError: Invalid record', False),
|
assert isinstance(fail_record.cause, ValueError)
|
||||||
)
|
assert str(fail_record.cause) == 'Invalid record'
|
||||||
|
|
||||||
assert processor.successes == [True, True]
|
assert processor.successes == [True, True]
|
||||||
assert processor.failures == [False]
|
assert processor.failures == [False]
|
||||||
@@ -30,9 +30,7 @@ def test_batch():
|
|||||||
with processor(records=(False,), handler=record_handler):
|
with processor(records=(False,), handler=record_handler):
|
||||||
processed_messages = processor.process()
|
processed_messages = processor.process()
|
||||||
|
|
||||||
assert processed_messages == (
|
assert processed_messages[0].status == Status.FAIL
|
||||||
Result(Status.FAIL, 'ValueError: Invalid record', False),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert processor.successes == []
|
assert processor.successes == []
|
||||||
assert processor.failures == [False]
|
assert processor.failures == [False]
|
||||||
@@ -50,7 +48,7 @@ def test_batch_context():
|
|||||||
processed_messages = processor.process()
|
processed_messages = processor.process()
|
||||||
|
|
||||||
assert processed_messages == (
|
assert processed_messages == (
|
||||||
Result(Status.SUCCESS, 4, 2),
|
Result(Status.SUCCESS, output=4, input_record=2),
|
||||||
Result(Status.SUCCESS, 6, 3),
|
Result(Status.SUCCESS, output=6, input_record=3),
|
||||||
Result(Status.SUCCESS, 8, 4),
|
Result(Status.SUCCESS, output=8, input_record=4),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
||||||
|
|
||||||
|
def get_dynamodb_client():
|
||||||
|
if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):
|
||||||
|
return boto3.client('dynamodb')
|
||||||
|
|
||||||
|
return boto3.client('dynamodb', endpoint_url='http://localhost:8000')
|
||||||
|
|
||||||
|
|
||||||
|
dynamodb_client = get_dynamodb_client()
|
||||||
s3_client = boto3.client('s3')
|
s3_client = boto3.client('s3')
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
|
||||||
CHUNK_SIZE = 50
|
CHUNK_SIZE = 50
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import csv
|
import csv
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from aws_lambda_powertools.utilities.data_classes import (
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
EventBridgeEvent,
|
EventBridgeEvent,
|
||||||
@@ -9,6 +10,11 @@ from aws_lambda_powertools.utilities.typing import LambdaContext
|
|||||||
|
|
||||||
from boto3clients import s3_client
|
from boto3clients import s3_client
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from mypy_boto3_s3.client import S3Client
|
||||||
|
else:
|
||||||
|
S3Client = object
|
||||||
|
|
||||||
transport_params = {'client': s3_client}
|
transport_params = {'client': s3_client}
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +42,7 @@ def _get_s3_object_range(
|
|||||||
*,
|
*,
|
||||||
start_byte: int,
|
start_byte: int,
|
||||||
end_byte: int,
|
end_byte: int,
|
||||||
s3_client,
|
s3_client: S3Client,
|
||||||
) -> StringIO:
|
) -> StringIO:
|
||||||
bucket, key = s3_uri.replace('s3://', '').split('/', 1)
|
bucket, key = s3_uri.replace('s3://', '').split('/', 1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import urllib.parse as urllib_parse
|
import urllib.parse as urllib_parse
|
||||||
from email.utils import parseaddr
|
from email.utils import parseaddr
|
||||||
from typing import Any, Iterator
|
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.utilities.data_classes import SESEvent, event_source
|
from aws_lambda_powertools.utilities.data_classes import SESEvent, event_source
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
|
||||||
|
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import USER_TABLE
|
||||||
|
from ses_utils import get_header_value
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
@logger.inject_lambda_context
|
@logger.inject_lambda_context
|
||||||
@@ -15,31 +20,21 @@ def lambda_handler(event: SESEvent, context: LambdaContext) -> dict:
|
|||||||
ses = event.record.ses
|
ses = event.record.ses
|
||||||
to = urllib_parse.unquote(ses.receipt.recipients[0]).lower()
|
to = urllib_parse.unquote(ses.receipt.recipients[0]).lower()
|
||||||
name, email_from = parseaddr(get_header_value(ses.mail.headers, 'from'))
|
name, email_from = parseaddr(get_header_value(ses.mail.headers, 'from'))
|
||||||
subject = get_header_value(
|
|
||||||
ses.mail.headers,
|
org_id = user_layer.collection.get_item(
|
||||||
'subject',
|
KeyPair('email', SortKey(to, path_spec='user_id')),
|
||||||
default='',
|
raise_on_error=False,
|
||||||
raise_on_missing=False,
|
default={},
|
||||||
)
|
)
|
||||||
|
|
||||||
if email_from == 'sergio@somosbeta.com.br':
|
if not org_id:
|
||||||
return {'disposition': 'CONTINUE'}
|
|
||||||
|
|
||||||
return {'disposition': 'STOP_RULE_SET'}
|
return {'disposition': 'STOP_RULE_SET'}
|
||||||
|
|
||||||
|
print(
|
||||||
|
{
|
||||||
|
'id': f'mailbox#{org_id}',
|
||||||
|
'sk': ses.mail.message_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def get_header_value(
|
return {'disposition': 'CONTINUE'}
|
||||||
headers: Iterator,
|
|
||||||
header_name: str,
|
|
||||||
*,
|
|
||||||
default: Any = None,
|
|
||||||
raise_on_missing: bool = True,
|
|
||||||
) -> str:
|
|
||||||
for header in headers:
|
|
||||||
if header.name.lower() == header_name:
|
|
||||||
return header.value
|
|
||||||
|
|
||||||
if raise_on_missing:
|
|
||||||
raise ValueError(f'{header_name} not found.')
|
|
||||||
|
|
||||||
return default
|
|
||||||
|
|||||||
@@ -25,8 +25,14 @@ Ignore all other fields.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
csv_content = """
|
csv_content = """
|
||||||
Sérgio Rafael de Siqueira,10,07879819908,osergiosiqueria@gmail.com,cipa
|
,RICARDO GALLES BONET,ricardo.bonet@fanucamerica.com,424.430.528-93,NR-10 (RECICLAGEM)
|
||||||
Tiago Maciel,12,086.790.049-01,tiago@somosbeta.com.br,nr 10
|
,RULIO SIEFERT SERA,rulio.sera@fanucamerica.com,063.916.859-08,NR-10 (RECICLAGEM)
|
||||||
|
,MACIEL FERREIRA BOMFIM,maciel.bomfim@fanucamerica.com,334.547.088-85,NR-10 (RECICLAGEM)
|
||||||
|
,JAIME EDUARDO GALVEZ AVILES,jaime.galvez@fanucamerica.com,280.238.818-50,NR-12
|
||||||
|
,JAIME EDUARDO GALVEZ AVILES,jaime.galvez@fanucamerica.com,280.238.818-50,NR-35 (RECICLAGEM)
|
||||||
|
,HIGOR MACHADO SILVA,higor.silva@fanucamerica.com,419.879.878-88,NR-12
|
||||||
|
,LÁZARO SOUZA DIAS,lazaro.dias@fanucamerica.com,067.179.825-19,NR-12
|
||||||
|
,JOÃO PEDRO AGUIAR GALASSO,joao.pedro@fanucamerica.com,570.403.588-40,NR-12
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ dependencies = ["layercake"]
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
|
"boto3-stubs[essential]>=1.38.26",
|
||||||
|
"jsonlines>=4.0.0",
|
||||||
"pytest>=8.3.4",
|
"pytest>=8.3.4",
|
||||||
"pytest-cov>=6.0.0",
|
"pytest-cov>=6.0.0",
|
||||||
"ruff>=0.9.1",
|
"ruff>=0.9.1",
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ Resources:
|
|||||||
Handler: events.email_receiving.lambda_handler
|
Handler: events.email_receiving.lambda_handler
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
|
Policies:
|
||||||
|
- DynamoDBCrudPolicy:
|
||||||
|
TableName: !Ref UserTable
|
||||||
|
|
||||||
LambdaInvokePermission:
|
LambdaInvokePermission:
|
||||||
Type: AWS::Lambda::Permission
|
Type: AWS::Lambda::Permission
|
||||||
@@ -72,7 +75,7 @@ Resources:
|
|||||||
Properties:
|
Properties:
|
||||||
Bucket: !Ref BucketName
|
Bucket: !Ref BucketName
|
||||||
PolicyDocument:
|
PolicyDocument:
|
||||||
Version: "2012-10-17"
|
Version: 2012-10-17
|
||||||
Statement:
|
Statement:
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Principal:
|
Principal:
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
|
import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import jsonlines
|
||||||
import pytest
|
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['DYNAMODB_PARTITION_KEY'] = PK
|
||||||
|
os.environ['DYNAMODB_SORT_KEY'] = SK
|
||||||
|
os.environ['USER_TABLE'] = PYTEST_TABLE_NAME
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LambdaContext:
|
class LambdaContext:
|
||||||
@@ -14,3 +28,42 @@ class LambdaContext:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def lambda_context() -> LambdaContext:
|
def lambda_context() -> LambdaContext:
|
||||||
return LambdaContext()
|
return LambdaContext()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dynamodb_client():
|
||||||
|
from boto3clients import dynamodb_client as client
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
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 dynamodb_seeds(dynamodb_client):
|
||||||
|
with jsonlines.open('tests/seeds.jsonl') as lines:
|
||||||
|
for line in lines:
|
||||||
|
dynamodb_client.put_item(TableName=PYTEST_TABLE_NAME, Item=line)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ event = {
|
|||||||
'source': 'sergio@somosbeta.com.br',
|
'source': 'sergio@somosbeta.com.br',
|
||||||
'messageId': '2994higq3tr7efijr3lj65etntffapgg1q7hea81',
|
'messageId': '2994higq3tr7efijr3lj65etntffapgg1q7hea81',
|
||||||
'destination': [
|
'destination': [
|
||||||
'org+35980592000130@users.noreply.saladeaula.digital'
|
'org+15608435000190@users.noreply.saladeaula.digital'
|
||||||
],
|
],
|
||||||
'headersTruncated': False,
|
'headersTruncated': False,
|
||||||
'headers': [
|
'headers': [
|
||||||
@@ -93,7 +93,7 @@ event = {
|
|||||||
{'name': 'Subject', 'value': 'Re: test'},
|
{'name': 'Subject', 'value': 'Re: test'},
|
||||||
{
|
{
|
||||||
'name': 'To',
|
'name': 'To',
|
||||||
'value': 'org+35980592000130@users.noreply.saladeaula.digital',
|
'value': 'org+15608435000190@users.noreply.saladeaula.digital',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Content-Type',
|
'name': 'Content-Type',
|
||||||
@@ -104,7 +104,7 @@ event = {
|
|||||||
'returnPath': 'sergio@somosbeta.com.br',
|
'returnPath': 'sergio@somosbeta.com.br',
|
||||||
'from': ['"Sérgio Rafael Siqueira" <sergio@somosbeta.com.br>'],
|
'from': ['"Sérgio Rafael Siqueira" <sergio@somosbeta.com.br>'],
|
||||||
'date': 'Thu, 29 May 2025 12:50:26 -0300',
|
'date': 'Thu, 29 May 2025 12:50:26 -0300',
|
||||||
'to': ['org+35980592000130@users.noreply.saladeaula.digital'],
|
'to': ['org+15608435000190@users.noreply.saladeaula.digital'],
|
||||||
'messageId': '<CAMThe4=yMRJg4YOcACYAR509N1RyWyQgAghyVmr=NuSJnbondg@mail.gmail.com>',
|
'messageId': '<CAMThe4=yMRJg4YOcACYAR509N1RyWyQgAghyVmr=NuSJnbondg@mail.gmail.com>',
|
||||||
'subject': 'Re: test',
|
'subject': 'Re: test',
|
||||||
},
|
},
|
||||||
@@ -113,7 +113,7 @@ event = {
|
|||||||
'timestamp': '2025-05-29T15:50:41.604Z',
|
'timestamp': '2025-05-29T15:50:41.604Z',
|
||||||
'processingTimeMillis': 1105,
|
'processingTimeMillis': 1105,
|
||||||
'recipients': [
|
'recipients': [
|
||||||
'org+35980592000130@users.noreply.saladeaula.digital'
|
'org+15608435000190@users.noreply.saladeaula.digital'
|
||||||
],
|
],
|
||||||
'spamVerdict': {'status': 'PASS'},
|
'spamVerdict': {'status': 'PASS'},
|
||||||
'virusVerdict': {'status': 'PASS'},
|
'virusVerdict': {'status': 'PASS'},
|
||||||
@@ -132,5 +132,5 @@ event = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_email_receiving(lambda_context: LambdaContext):
|
def test_email_receiving(dynamodb_seeds, lambda_context: LambdaContext):
|
||||||
assert app.lambda_handler(event, lambda_context) == {'disposition': 'CONTINUE'}
|
assert app.lambda_handler(event, lambda_context) == {'disposition': 'CONTINUE'}
|
||||||
|
|||||||
133
user-management/uv.lock
generated
133
user-management/uv.lock
generated
@@ -103,6 +103,30 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ce/89/634155fb209f50fd98da1cb11480bcdf6ed8d8ab68800d91cdb2bf59a8af/boto3-1.38.17-py3-none-any.whl", hash = "sha256:9b56c98fe7acb6559c24dacd838989878c60f3df2fb8ca5f311128419fd9f953", size = 139937, upload-time = "2025-05-15T19:35:14.663Z" },
|
{ url = "https://files.pythonhosted.org/packages/ce/89/634155fb209f50fd98da1cb11480bcdf6ed8d8ab68800d91cdb2bf59a8af/boto3-1.38.17-py3-none-any.whl", hash = "sha256:9b56c98fe7acb6559c24dacd838989878c60f3df2fb8ca5f311128419fd9f953", size = 139937, upload-time = "2025-05-15T19:35:14.663Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boto3-stubs"
|
||||||
|
version = "1.38.26"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "botocore-stubs" },
|
||||||
|
{ name = "types-s3transfer" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1e/89/9e2658210ac11459405ed8c82f47be533a5d16ae3b8203c90564b7a738a0/boto3_stubs-1.38.26.tar.gz", hash = "sha256:492e59e42323de43018ffa6d00d3bb2b93d1fead042e76c6a68fd0a0c0fe3236", size = 99065, upload-time = "2025-05-29T19:47:49.383Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/51/881ac3c7ebeefdfe0e712b1f4d815261707a492187ea301506168cf6fc20/boto3_stubs-1.38.26-py3-none-any.whl", hash = "sha256:3022b2a8f6925c60c9ce68c5e090ff9fd2bad0c918300395a1c242681a67c11c", size = 68669, upload-time = "2025-05-29T19:47:42.352Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[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.17"
|
version = "1.38.17"
|
||||||
@@ -117,6 +141,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/01/fc/9c08db2e89055999e996fee3537cbbfb4ed8ebf0d4ab1b1045e1819b76d8/botocore-1.38.17-py3-none-any.whl", hash = "sha256:ec75cf02fbd3dbec18187085ce387761eab16afdccfd0774fd168db3689c6cb6", size = 13564514, upload-time = "2025-05-15T19:35:00.231Z" },
|
{ url = "https://files.pythonhosted.org/packages/01/fc/9c08db2e89055999e996fee3537cbbfb4ed8ebf0d4ab1b1045e1819b76d8/botocore-1.38.17-py3-none-any.whl", hash = "sha256:ec75cf02fbd3dbec18187085ce387761eab16afdccfd0774fd168db3689c6cb6", size = 13564514, upload-time = "2025-05-15T19:35:00.231Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "botocore-stubs"
|
||||||
|
version = "1.38.26"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "types-awscrt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/08/f2/fd2f5a8ef00bbcfe00c12b8c49e247510266929dff5578b6fec360967a21/botocore_stubs-1.38.26.tar.gz", hash = "sha256:3bbf7662fc97e28a50dc959752619cf57029194987268b4dc13df4e54767204c", size = 42315, upload-time = "2025-05-29T20:18:25.22Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/4a/f99ef1ff37620c0c23aa67e3d9de5fce0f98b38fd26e6d30438ee440c0fc/botocore_stubs-1.38.26-py3-none-any.whl", hash = "sha256:c86ac7d2c7e24ea50a866a9686a293dfe8b40281cc3465d79e2e0e48d35ad93b", size = 65628, upload-time = "2025-05-29T20:18:23.125Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -469,6 +505,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 = "jsonlines"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "attrs" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359, upload-time = "2023-09-01T12:34:44.187Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701, upload-time = "2023-09-01T12:34:42.563Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonpath-ng"
|
name = "jsonpath-ng"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@@ -545,6 +593,69 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/bf/3e/a3ec8d44b35e495444cac8ce3573b33adf19a9b6d70f2a51e4a971f17c81/meilisearch-0.34.1-py3-none-any.whl", hash = "sha256:43efa4521ce7dc3b065d404267ad5b3acb825602e6219b8b5356650306686cd4", size = 24918, upload-time = "2025-04-04T13:45:06.869Z" },
|
{ url = "https://files.pythonhosted.org/packages/bf/3e/a3ec8d44b35e495444cac8ce3573b33adf19a9b6d70f2a51e4a971f17c81/meilisearch-0.34.1-py3-none-any.whl", hash = "sha256:43efa4521ce7dc3b065d404267ad5b3acb825602e6219b8b5356650306686cd4", size = 24918, upload-time = "2025-04-04T13:45:06.869Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-cloudformation"
|
||||||
|
version = "1.38.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/40/d5/35b9301c8b2fb870e58401d13fec36de2c83f2ddef48398b8c89c9a58995/mypy_boto3_cloudformation-1.38.0.tar.gz", hash = "sha256:563399166c07e91e0695fb1e58103a248b2bee0db5e2c3f07155776dd6311805", size = 57702, upload-time = "2025-04-22T21:19:31.221Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/2f/d20ad6e0113f982ea255fcb4ed94f70d0111757d7d03bfacebc2d9f60ba4/mypy_boto3_cloudformation-1.38.0-py3-none-any.whl", hash = "sha256:a1411aa5875b737492aaac5f7e8ce450f034c18f972eb608a9eba6fe35837f6a", size = 69607, upload-time = "2025-04-22T21:19:29.235Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-dynamodb"
|
||||||
|
version = "1.38.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6b/7f/72b68d275a80a42675c36249b80dd79ec5c7d9bd1f5cc93cdb572f866722/mypy_boto3_dynamodb-1.38.4.tar.gz", hash = "sha256:5cf3787631e312b3d75f89a6cbbbd4ad786a76f5d565af023febf03fbf23c0b5", size = 47461, upload-time = "2025-04-28T19:26:22.728Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/35/3d0ceabb0a9f3765f509cb9dce6ddfa939114682b1acc442f52a755e9bc8/mypy_boto3_dynamodb-1.38.4-py3-none-any.whl", hash = "sha256:6b29d89c649eeb1e894118bee002cb8b1304c78da735b1503aa08e46b0abfdec", size = 56395, upload-time = "2025-04-28T19:26:16.947Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-ec2"
|
||||||
|
version = "1.38.25"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d4/88/b9a99e010224b2ad9ea45b96f6689d9706841f2788e5ffe114cb0041e543/mypy_boto3_ec2-1.38.25.tar.gz", hash = "sha256:aed7d746c7c6af7e3f75424ad64829a7ce5b94dc871114a449c403ada22954cb", size = 400494, upload-time = "2025-05-28T19:42:21.197Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/22/f7caf22d014c7b0b62546a41b49030b73c162702870bfd1b1aaf48e2cc05/mypy_boto3_ec2-1.38.25-py3-none-any.whl", hash = "sha256:bad444d731669eab25fdcb7259901cb0db0fb26a4e1a79836a32aef6d674dbd0", size = 389882, upload-time = "2025-05-28T19:42:17.484Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-lambda"
|
||||||
|
version = "1.38.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8f/e3/0436071b28942788bdd22d6f91847654a7b1d167fb9d86c5779108e49ee9/mypy_boto3_lambda-1.38.0.tar.gz", hash = "sha256:ece7b3848c045e1be81c4f2b7482002c17ce7cb70de850661146103a8cb1a3fb", size = 41767, upload-time = "2025-04-22T21:27:54.666Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/09/602a39b39abd0d58d8b6bbee4c1552b64fadba2324676d7d45c3fa00fe7b/mypy_boto3_lambda-1.38.0-py3-none-any.whl", hash = "sha256:0dcb882826f61fd2751f6b98330b0e11085570654db85318aea018374ca88dc9", size = 48210, upload-time = "2025-04-22T21:27:52.034Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-rds"
|
||||||
|
version = "1.38.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/97/b7/5ac46bee6617c8f955f6c28b8698a954f537812d84d655e3c887557421f0/mypy_boto3_rds-1.38.20.tar.gz", hash = "sha256:c6aa70c0cc5bc59959fec434206fbf8200386b583ff1f7e372154eaa41eb52e9", size = 85121, upload-time = "2025-05-20T23:30:09.631Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/ff/1a82134124ef41c040a40076c83b111d760f57007ec7b16649891fef0473/mypy_boto3_rds-1.38.20-py3-none-any.whl", hash = "sha256:9f600c24e687780fed1c8dc6d244b17dd0889f34705ec40c66df15e1caa420f4", size = 91368, upload-time = "2025-05-20T23:30:05.508Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-s3"
|
||||||
|
version = "1.38.26"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/79/a5/366aec375b77cfe7820b7b3213318b147aefda6f12a035691541a5d557d1/mypy_boto3_s3-1.38.26.tar.gz", hash = "sha256:38a45dee5782d5c07ddea07ea50965c4d2ba7e77617c19f613b4c9f80f961b52", size = 73717, upload-time = "2025-05-29T19:43:03.468Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/fa/251b651c18341c7491909994bd459b12ad05e13059d65bfa65d3afabdf8d/mypy_boto3_s3-1.38.26-py3-none-any.whl", hash = "sha256:1129d64be1aee863e04f0c92ac8d315578f13ccae64fa199b20ad0950d2b9616", size = 80321, upload-time = "2025-05-29T19:42:59.199Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-boto3-sqs"
|
||||||
|
version = "1.38.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/a0/ef5c7bdb33af5d0a48029fed11401388fa68949c6c0f9b11b2e845f5fe0e/mypy_boto3_sqs-1.38.0.tar.gz", hash = "sha256:39aebc121a2fe20f962fd83b617fd916003605d6f6851fdf195337a0aa428fe1", size = 23541, upload-time = "2025-04-22T21:35:17.315Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/97/72fccc9aaa0e3c8f3f99b4edac580ede651808aefb47b0d2b52c18a3d16b/mypy_boto3_sqs-1.38.0-py3-none-any.whl", hash = "sha256:8e881c8492f6f51dcbe1cce9d9f05334f4b256b5843e227fa925e0f6e702b31d", size = 33669, upload-time = "2025-04-22T21:35:16.073Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.10.18"
|
version = "3.10.18"
|
||||||
@@ -896,6 +1007,24 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5c/de/27c57899297163a4a84104d5cec0af3b1ac5faf62f44667e506373c6b8ce/tinyhtml5-2.0.0-py3-none-any.whl", hash = "sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e", size = 39793, upload-time = "2024-10-29T15:37:11.743Z" },
|
{ url = "https://files.pythonhosted.org/packages/5c/de/27c57899297163a4a84104d5cec0af3b1ac5faf62f44667e506373c6b8ce/tinyhtml5-2.0.0-py3-none-any.whl", hash = "sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e", size = 39793, upload-time = "2024-10-29T15:37:11.743Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-awscrt"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/36/6c/583522cfb3c330e92e726af517a91c13247e555e021791a60f1b03c6ff16/types_awscrt-0.27.2.tar.gz", hash = "sha256:acd04f57119eb15626ab0ba9157fc24672421de56e7bd7b9f61681fedee44e91", size = 16304, upload-time = "2025-05-16T03:10:08.712Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/82/1ee2e5c9d28deac086ab3a6ff07c8bc393ef013a083f546c623699881715/types_awscrt-0.27.2-py3-none-any.whl", hash = "sha256:49a045f25bbd5ad2865f314512afced933aed35ddbafc252e2268efa8a787e4e", size = 37761, upload-time = "2025-05-16T03:10:07.466Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-s3transfer"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/42/c1/45038f259d6741c252801044e184fec4dbaeff939a58f6160d7c32bf4975/types_s3transfer-0.13.0.tar.gz", hash = "sha256:203dadcb9865c2f68fb44bc0440e1dc05b79197ba4a641c0976c26c9af75ef52", size = 14175, upload-time = "2025-05-28T02:16:07.614Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/5d/6bbe4bf6a79fb727945291aef88b5ecbdba857a603f1bbcf1a6be0d3f442/types_s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:79c8375cbf48a64bff7654c02df1ec4b20d74f8c5672fc13e382f593ca5565b3", size = 19588, upload-time = "2025-05-28T02:16:06.709Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.13.2"
|
version = "4.13.2"
|
||||||
@@ -936,6 +1065,8 @@ dependencies = [
|
|||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
|
{ name = "boto3-stubs", extra = ["essential"] },
|
||||||
|
{ name = "jsonlines" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pytest-cov" },
|
{ name = "pytest-cov" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
@@ -946,6 +1077,8 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }]
|
|||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
|
{ name = "boto3-stubs", extras = ["essential"], specifier = ">=1.38.26" },
|
||||||
|
{ name = "jsonlines", specifier = ">=4.0.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 = "ruff", specifier = ">=0.9.1" },
|
{ name = "ruff", specifier = ">=0.9.1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user