add reset password

This commit is contained in:
2025-04-14 10:49:28 -03:00
parent 273c580139
commit e472826dcc
17 changed files with 228 additions and 56 deletions

View File

@@ -12,7 +12,7 @@ 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.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from middlewares import AuthorizerMiddleware from middlewares import AuthenticationMiddleware
from routes import courses, enrollments, lookup, orders, orgs, settings, users, webhooks from routes import courses, enrollments, lookup, orders, orgs, settings, users, webhooks
tracer = Tracer() tracer = Tracer()
@@ -28,7 +28,7 @@ app = APIGatewayHttpResolver(
cors=cors, cors=cors,
debug='AWS_SAM_LOCAL' in os.environ, debug='AWS_SAM_LOCAL' in os.environ,
) )
app.use(middlewares=[AuthorizerMiddleware()]) app.use(middlewares=[AuthenticationMiddleware()])
app.include_router(courses.router, prefix='/courses') app.include_router(courses.router, prefix='/courses')
app.include_router(enrollments.router, prefix='/enrollments') app.include_router(enrollments.router, prefix='/enrollments')
app.include_router(orders.router, prefix='/orders') app.include_router(orders.router, prefix='/orders')

View File

@@ -36,3 +36,40 @@ def admin_get_user(
return None return None
else: else:
return user return user
def admin_set_user_password(
username: str,
password: str,
*,
user_pool_id: str,
permanent: bool = False,
idp_client,
) -> bool:
"""Sets the specified user's password in a user pool as an administrator.
Works on any user.
The password can be temporary or permanent. If it is temporary, the user
status enters the FORCE_CHANGE_PASSWORD state.
When the user next tries to sign in, the InitiateAuth/AdminInitiateAuth
response will contain the NEW_PASSWORD_REQUIRED challenge.
If the user doesn't sign in before it expires, the user won't be able
to sign in, and an administrator must reset their password.
Once the user has set a new password, or the password is permanent,
the user status is set to Confirmed.
"""
try:
idp_client.admin_set_user_password(
UserPoolId=user_pool_id,
Username=username,
Password=password,
Permanent=permanent,
)
except idp_client.exceptions as err:
logger.exception(err)
return False
else:
return True

View File

@@ -1,9 +1,9 @@
from .audit_log_middleware import AuditLogMiddleware from .audit_log_middleware import AuditLogMiddleware
from .authorizer_middleware import AuthorizerMiddleware, User from .authentication_middleware import AuthenticationMiddleware, User
from .tenant_middelware import Tenant, TenantMiddleware from .tenant_middelware import Tenant, TenantMiddleware
__all__ = [ __all__ = [
'AuthorizerMiddleware', 'AuthenticationMiddleware',
'AuditLogMiddleware', 'AuditLogMiddleware',
'TenantMiddleware', 'TenantMiddleware',
'User', 'User',

View File

@@ -17,7 +17,7 @@ from layercake.dynamodb import (
) )
from layercake.funcs import pick from layercake.funcs import pick
from .authorizer_middleware import User from .authentication_middleware import User
YEAR_DAYS = 365 YEAR_DAYS = 365
LOG_RETENTION_DAYS = YEAR_DAYS * 2 LOG_RETENTION_DAYS = YEAR_DAYS * 2
@@ -64,10 +64,13 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
ip_addr = req_context.http.source_ip ip_addr = req_context.http.source_ip
response = next_middleware(app) response = next_middleware(app)
print(app.context['_route'])
# Successful response # Successful response
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#successful_responses # https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#successful_responses
if 200 <= response.status_code < 300 and user: if 200 <= response.status_code < 300 and user:
now_ = now() now_ = now()
author = pick(('id', 'name'), dict(user))
data = ( data = (
pick(self.audit_attrs, extract_event_from_common_models(response.body)) pick(self.audit_attrs, extract_event_from_common_models(response.body))
if response.is_json() if response.is_json()
@@ -89,7 +92,7 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
action=self.action, action=self.action,
data=data, data=data,
ip=ip_addr, ip=ip_addr,
author='himself', author=author,
ttl=retention_days, ttl=retention_days,
) )

View File

@@ -23,7 +23,7 @@ class CognitoUser(User):
sub: UUID4 sub: UUID4
class AuthorizerMiddleware(BaseMiddlewareHandler): class AuthenticationMiddleware(BaseMiddlewareHandler):
"""This middleware extracts user authentication details from the Lambda authorizer context """This middleware extracts user authentication details from the Lambda authorizer context
and makes them available in the application context.""" and makes them available in the application context."""

View File

@@ -18,7 +18,7 @@ from pydantic import UUID4, BaseModel
from auth import AuthFlowType from auth import AuthFlowType
from .authorizer_middleware import User from .authentication_middleware import User
class Tenant(BaseModel): class Tenant(BaseModel):

View File

@@ -15,7 +15,7 @@ import elastic
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from enrollment import set_status_as_canceled from enrollment import set_status_as_canceled
from middlewares.audit_log_middleware import AuditLogMiddleware from middlewares.audit_log_middleware import AuditLogMiddleware
from middlewares.authorizer_middleware import User from middlewares.authentication_middleware import User
from settings import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE from settings import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE
router = Router() router = Router()

View File

@@ -24,6 +24,7 @@ from pydantic import UUID4, BaseModel, EmailStr, StringConstraints
import cognito import cognito
import elastic import elastic
import middlewares
from boto3clients import dynamodb_client, idp_client from boto3clients import dynamodb_client, idp_client
from middlewares import AuditLogMiddleware from middlewares import AuditLogMiddleware
from models import User from models import User
@@ -70,9 +71,31 @@ class Password(BaseModel):
new_password: Annotated[str, StringConstraints(min_length=6)] new_password: Annotated[str, StringConstraints(min_length=6)]
@router.post('/<id>/password', compress=True, tags=['User'], include_in_schema=False) @router.post(
def new_password(id: str, payload: Password): '/<id>/password',
return Response(status_code=HTTPStatus.OK) compress=True,
tags=['User'],
include_in_schema=False,
middlewares=[
AuditLogMiddleware('PASSWORD_RESET', user_collect, ('id', 'cognito_sub'))
],
)
def password(id: str, payload: Password):
cognito.admin_set_user_password(
username=str(payload.cognito_sub),
password=payload.new_password,
user_pool_id=USER_POOOL_ID,
idp_client=idp_client,
)
return Response(
body={
'id': id,
'cognito_sub': payload.cognito_sub,
},
content_type=content_types.APPLICATION_JSON,
status_code=HTTPStatus.OK,
)
@router.get('/<id>', compress=True, tags=['User'], summary='Get user') @router.get('/<id>', compress=True, tags=['User'], summary='Get user')
@@ -80,10 +103,10 @@ def get_user(id: str):
return user_collect.get_item(KeyPair(id, '0')) return user_collect.get_item(KeyPair(id, '0'))
@router.get('/<id>/idp', compress=True, include_in_schema=False) @router.get('/<sub>/idp', compress=True, include_in_schema=False)
def get_idp(id: str): def get_idp(sub: str):
return cognito.admin_get_user( return cognito.admin_get_user(
sub=id, sub=sub,
user_pool_id=USER_POOOL_ID, user_pool_id=USER_POOOL_ID,
idp_client=idp_client, idp_client=idp_client,
) )

View File

@@ -14,7 +14,7 @@ from ..conftest import HttpApiProxy, LambdaContext
YEAR_DAYS = 365 YEAR_DAYS = 365
def test_get_course( def test_get_courses(
mock_app, mock_app,
dynamodb_seeds, dynamodb_seeds,
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,

View File

@@ -1,11 +1,7 @@
import json import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import ( from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
DynamoDBCollection,
DynamoDBPersistenceLayer,
KeyPair,
)
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
@@ -32,7 +28,7 @@ def test_get_policies(
} }
def test_put_org( def test_put_policies(
mock_app, mock_app,
dynamodb_seeds, dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer, dynamodb_persistence_layer: DynamoDBPersistenceLayer,
@@ -44,7 +40,7 @@ def test_put_org(
raw_path='/orgs/cJtK9SsnJhKPyxESe7g3DG/policies', raw_path='/orgs/cJtK9SsnJhKPyxESe7g3DG/policies',
method=HTTPMethod.PUT, method=HTTPMethod.PUT,
headers={'X-Tenant': '*'}, headers={'X-Tenant': '*'},
body={}, body={'payment_policy': None},
), ),
lambda_context, lambda_context,
) )

View File

@@ -31,7 +31,16 @@ def test_get_emails(
'id': '5OxmMjL-ujoR5IMGegQz', 'id': '5OxmMjL-ujoR5IMGegQz',
'create_date': '2019-03-25T00:00:00-03:00', 'create_date': '2019-03-25T00:00:00-03:00',
'update_date': '2023-11-09T12:13:04.308986-03:00', 'update_date': '2023-11-09T12:13:04.308986-03:00',
} },
{
'email_verified': True,
'mx_record_exists': True,
'sk': 'osergiosiqueira@gmail.com',
'email_primary': False,
'id': '5OxmMjL-ujoR5IMGegQz',
'create_date': '2019-03-25T00:00:00-03:00',
'update_date': '2023-11-09T12:13:04.308986-03:00',
},
], ],
'last_key': None, 'last_key': None,
} }

View File

@@ -4,7 +4,7 @@ import pytest
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
from middlewares import AuthorizerMiddleware, TenantMiddleware from middlewares import AuthenticationMiddleware, TenantMiddleware
from .conftest import HttpApiProxy, LambdaContext from .conftest import HttpApiProxy, LambdaContext
@@ -13,7 +13,7 @@ from .conftest import HttpApiProxy, LambdaContext
def mock_app(dynamodb_persistence_layer: DynamoDBPersistenceLayer): def mock_app(dynamodb_persistence_layer: DynamoDBPersistenceLayer):
collect = DynamoDBCollection(dynamodb_persistence_layer) collect = DynamoDBCollection(dynamodb_persistence_layer)
app = APIGatewayHttpResolver() app = APIGatewayHttpResolver()
app.use(middlewares=[AuthorizerMiddleware(), TenantMiddleware(collect)]) app.use(middlewares=[AuthenticationMiddleware(), TenantMiddleware(collect)])
@app.get('/') @app.get('/')
def index(): def index():

View File

@@ -67,7 +67,7 @@ def del_email(
key=KeyPair('email', email), key=KeyPair('email', email),
) )
transact.delete( transact.delete(
key=KeyPair(id, ComposeKey(email, 'emails')), key=KeyPair(id, ComposeKey(email, prefix='emails')),
cond_expr='email_primary <> :primary', cond_expr='email_primary <> :primary',
expr_attr_values={':primary': True}, expr_attr_values={':primary': True},
) )

View File

@@ -10,3 +10,6 @@ deploy: export build
.PHONY: docs .PHONY: docs
docs: docs:
uv run mkdocs build uv run mkdocs build
docs_serve:
uv run mkdocs serve --dev-addr localhost:8080

View File

@@ -421,8 +421,12 @@ class TransactItems:
) )
if TYPE_CHECKING:
from mypy_boto3_dynamodb.client import DynamoDBClient
class DynamoDBPersistenceLayer: class DynamoDBPersistenceLayer:
def __init__(self, table_name: str, dynamodb_client) -> None: def __init__(self, table_name: str, dynamodb_client: DynamoDBClient) -> None:
self.table_name = table_name self.table_name = table_name
self.dynamodb_client = dynamodb_client self.dynamodb_client = dynamodb_client

View File

@@ -23,11 +23,11 @@ dependencies = [
"meilisearch>=0.34.0", "meilisearch>=0.34.0",
"arnparse>=0.0.2", "arnparse>=0.0.2",
"weasyprint>=65.0", "weasyprint>=65.0",
"uuid-utils>=0.10.0",
] ]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"boto3-stubs[essential]>=1.37.33",
"jsonlines>=4.0.0", "jsonlines>=4.0.0",
"mkdocstrings[python]>=0.29.0", "mkdocstrings[python]>=0.29.0",
"pytest>=8.3.5", "pytest>=8.3.5",

143
layercake/uv.lock generated
View File

@@ -102,6 +102,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/09/7f/d667f5be40ccc21fed232f26473b352c6e329298e495b3cf230aef889348/boto3-1.37.16-py3-none-any.whl", hash = "sha256:7ba243b8f9e11ea8856b1875293f1d43f3aa04960d823f2016cb624f47d45048", size = 139562 }, { url = "https://files.pythonhosted.org/packages/09/7f/d667f5be40ccc21fed232f26473b352c6e329298e495b3cf230aef889348/boto3-1.37.16-py3-none-any.whl", hash = "sha256:7ba243b8f9e11ea8856b1875293f1d43f3aa04960d823f2016cb624f47d45048", size = 139562 },
] ]
[[package]]
name = "boto3-stubs"
version = "1.37.33"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore-stubs" },
{ name = "types-s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/3972bf3a03e38d06ec61d42fe81f25edc84d70722a716065c84bd2e27efb/boto3_stubs-1.37.33.tar.gz", hash = "sha256:d0561aadb98fa4ecffd34f34c3b8be3abd0a0e5de1367f7d3e985eb4de904c5b", size = 99118 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/f6/aad1bd83b17c96d69bcbda5b679d8f17f015712bac25aad0e25d4b6b7470/boto3_stubs-1.37.33-py3-none-any.whl", hash = "sha256:b811b9c99bb47300efead119af7cfc4496545b9e571dde62e7447901966e74f8", size = 68644 },
]
[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.16" version = "1.37.16"
@@ -116,6 +140,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/e9/98d3f5135cce841b54a8952f244db906511acba624252559e21188f84e90/botocore-1.37.16-py3-none-any.whl", hash = "sha256:d74d04830ead12933a96dc407175ae98b32a5dd0059d7d2b28fc7aa4ed9d3b48", size = 13422674 }, { url = "https://files.pythonhosted.org/packages/d2/e9/98d3f5135cce841b54a8952f244db906511acba624252559e21188f84e90/botocore-1.37.16-py3-none-any.whl", hash = "sha256:d74d04830ead12933a96dc407175ae98b32a5dd0059d7d2b28fc7aa4ed9d3b48", size = 13422674 },
] ]
[[package]]
name = "botocore-stubs"
version = "1.37.29"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "types-awscrt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/21/84/3c6f85a562fc2c0b6856a7742ccc0f59c7ff1ea2595e0159bc3b12ff7122/botocore_stubs-1.37.29.tar.gz", hash = "sha256:c59898bf1d09bf6a9f491f4705c5696e74b83156b766aa1716867f11b8a04ea1", size = 42239 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/26/8857ff237c8d70fb8591575749468eb8973e16c65931226b856dda50c160/botocore_stubs-1.37.29-py3-none-any.whl", hash = "sha256:923127abb5fac0b8b0f11837a4641f2863bb4398b5bd6b11d1604134966c4bb6", size = 65569 },
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "1.1.0" version = "1.1.0"
@@ -600,7 +636,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.2.8" version = "0.2.9"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },
@@ -617,12 +653,12 @@ dependencies = [
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "uuid-utils" },
{ name = "weasyprint" }, { name = "weasyprint" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "boto3-stubs", extra = ["essential"] },
{ name = "jsonlines" }, { name = "jsonlines" },
{ name = "mkdocstrings", extra = ["python"] }, { name = "mkdocstrings", extra = ["python"] },
{ name = "pytest" }, { name = "pytest" },
@@ -647,12 +683,12 @@ requires-dist = [
{ name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "pytz", specifier = ">=2025.1" }, { name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" }, { name = "requests", specifier = ">=2.32.3" },
{ name = "uuid-utils", specifier = ">=0.10.0" },
{ name = "weasyprint", specifier = ">=65.0" }, { name = "weasyprint", specifier = ">=65.0" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "boto3-stubs", extras = ["essential"], specifier = ">=1.37.33" },
{ name = "jsonlines", specifier = ">=4.0.0" }, { name = "jsonlines", specifier = ">=4.0.0" },
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.29.0" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.29.0" },
{ name = "pytest", specifier = ">=8.3.5" }, { name = "pytest", specifier = ">=8.3.5" },
@@ -818,6 +854,69 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/67/d0/ef6e82f7a68c7ac02e1a01815fbe88773f4f9e40728ed35bd1664a5d76f2/mkdocstrings_python-1.16.8-py3-none-any.whl", hash = "sha256:211b7aaf776cd45578ecb531e5ad0d3a35a8be9101a6bfa10de38a69af9d8fd8", size = 124116 }, { url = "https://files.pythonhosted.org/packages/67/d0/ef6e82f7a68c7ac02e1a01815fbe88773f4f9e40728ed35bd1664a5d76f2/mkdocstrings_python-1.16.8-py3-none-any.whl", hash = "sha256:211b7aaf776cd45578ecb531e5ad0d3a35a8be9101a6bfa10de38a69af9d8fd8", size = 124116 },
] ]
[[package]]
name = "mypy-boto3-cloudformation"
version = "1.37.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4a/43/69eef04d4da88a002b388b1789b766f9f6140cde44491d62f05f579bbe3e/mypy_boto3_cloudformation-1.37.22.tar.gz", hash = "sha256:bf6bed27d1ad82d1156964adabeb0d627b555ab870d689305a160cb93e0ef255", size = 57670 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/8b/801446e0ec5174601f04a42679d6051d0f5d3986b0cfbca8a8d994769a87/mypy_boto3_cloudformation-1.37.22-py3-none-any.whl", hash = "sha256:c411740ee8d48cd497a2555fd1a525b6e521631e58a1a7f6c863f5ee6005d178", size = 69619 },
]
[[package]]
name = "mypy-boto3-dynamodb"
version = "1.37.33"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/35/25f710ea66927a3b45a7fc0664328c95a43894883d40ac2fe3f16fd10180/mypy_boto3_dynamodb-1.37.33.tar.gz", hash = "sha256:f924999d45fc23dd080b30b4d10498627220bf8d3f47824a513e43445b9ea02d", size = 47468 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/7d/a648593b9e0a61d5fefd132ec578b31aeba86c6b16961150948e5cd8e70c/mypy_boto3_dynamodb-1.37.33-py3-none-any.whl", hash = "sha256:3c0325a495b0487ef1dbd0aa6578d5892862b226461cdcda7d87cb48c20f1e8b", size = 56405 },
]
[[package]]
name = "mypy-boto3-ec2"
version = "1.37.28"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/48/97/e137d815d435587d4764403be228c014536c5fdbc4eafe475c9395e57d8a/mypy_boto3_ec2-1.37.28.tar.gz", hash = "sha256:771d2004cfdff9d4dc7cf6e4903fe907c327007a4d704e009a3b1e6b00ae9df4", size = 393729 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/1e/678f7ac49ad21722541cc0769c3cdc73ea52df2cce714062091bbfa798b6/mypy_boto3_ec2-1.37.28-py3-none-any.whl", hash = "sha256:67ce870ed108140f6532a51b5964eecf495690d6fd88fe9a593964f674dc5adb", size = 383233 },
]
[[package]]
name = "mypy-boto3-lambda"
version = "1.37.16"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/de/09/d7dd80cabde53fe7f39a9d7f6342b59af14707bb775606b1d9ffe82efa8b/mypy_boto3_lambda-1.37.16.tar.gz", hash = "sha256:d58f20bb0416aeb04fda6cfaa8a2f2c31532ca5009004c3e2bcbe8ac50a8cf7c", size = 41727 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/0b/6d6256885c2de708d21782ba4ab1d2042c98095ca381e983cb3aeeac5810/mypy_boto3_lambda-1.37.16-py3-none-any.whl", hash = "sha256:22dd22f29ef77febad9f4a2ec3d4560385b9b19a4ca1909de863206392145273", size = 48225 },
]
[[package]]
name = "mypy-boto3-rds"
version = "1.37.21"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2b/e7/d4ed2d847739795bc4e7e336b2a89676db18a5011eeb0d48910275f15059/mypy_boto3_rds-1.37.21.tar.gz", hash = "sha256:8fa78d077226489fef28d1b3449646882425d259f93523f7b08afd9b761dfdf8", size = 84345 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/50/b722981a6a5506513f6cd0222cf014235c642e8d3880561f2809c8262dd8/mypy_boto3_rds-1.37.21-py3-none-any.whl", hash = "sha256:af163f0e97e54b55155bfe57dc1e292edc6a1d5c2e5c9d01e62c28c952c92f2c", size = 90492 },
]
[[package]]
name = "mypy-boto3-s3"
version = "1.37.24"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0e/1d/8ddb398335991d7f04c3cbc64232aea673c877146f3de5bd7cbea88f1d4f/mypy_boto3_s3-1.37.24.tar.gz", hash = "sha256:4df0975256132ab452896b9d36571866a816157d519cf12c661795b2906e2e9c", size = 73709 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/fb/17efbbde4fbbf027078e7577a38dc26c68e608deed8c31627328849c5825/mypy_boto3_s3-1.37.24-py3-none-any.whl", hash = "sha256:8afd8d64be352652bc888ed81750788bebabd2b6e8ee6fe9be00ff25228df39b", size = 80318 },
]
[[package]]
name = "mypy-boto3-sqs"
version = "1.37.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/c9/ebbd0efd7225b3eac16b409ac9a6f14250bc00d123b04c9255e7a482afa3/mypy_boto3_sqs-1.37.0.tar.gz", hash = "sha256:ed56df72494425d4e7d0e048f2321cc17514fa3bbedfc410bbb8b709c4bff564", size = 23508 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/8e/45f49fadc0cdea30c5d0286f17849821dd24ee95adffecd901d92dff2588/mypy_boto3_sqs-1.37.0-py3-none-any.whl", hash = "sha256:2622aa681386cdbffdfcfa638dab0e081054ff9d6fe495758f4a817bcc8ec6ed", size = 33611 },
]
[[package]] [[package]]
name = "orjson" name = "orjson"
version = "3.10.15" version = "3.10.15"
@@ -1261,6 +1360,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/de/27c57899297163a4a84104d5cec0af3b1ac5faf62f44667e506373c6b8ce/tinyhtml5-2.0.0-py3-none-any.whl", hash = "sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e", size = 39793 }, { url = "https://files.pythonhosted.org/packages/5c/de/27c57899297163a4a84104d5cec0af3b1ac5faf62f44667e506373c6b8ce/tinyhtml5-2.0.0-py3-none-any.whl", hash = "sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e", size = 39793 },
] ]
[[package]]
name = "types-awscrt"
version = "0.26.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/db/29/d0597055f1b700193463361b4b2284cf4548acbb10287d6253ce429728d5/types_awscrt-0.26.1.tar.gz", hash = "sha256:aca96f889b3745c0e74f42f08f277fed3bf6e9baa2cf9b06a36f78d77720e504", size = 15537 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/c0/4708ee11a50d4e80c4dfb88dc2d6d69b1c296cd7397f1fe6352f119fd310/types_awscrt-0.26.1-py3-none-any.whl", hash = "sha256:176d320a26990efc057d4bf71396e05be027c142252ac48cc0d87aaea0704280", size = 19575 },
]
[[package]]
name = "types-s3transfer"
version = "0.11.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/93/a9/440d8ba72a81bcf2cc5a56ef63f23b58ce93e7b9b62409697553bdcdd181/types_s3transfer-0.11.4.tar.gz", hash = "sha256:05fde593c84270f19fd053f0b1e08f5a057d7c5f036b9884e68fb8cd3041ac30", size = 14074 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/69/0b5ae42c3c33d31a32f7dcb9f35a3e327365360a6e4a2a7b491904bd38aa/types_s3transfer-0.11.4-py3-none-any.whl", hash = "sha256:2a76d92c07d4a3cb469e5343b2e7560e0b8078b2e03696a65407b8c44c861b61", size = 19516 },
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"
@@ -1279,26 +1396,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
] ]
[[package]]
name = "uuid-utils"
version = "0.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/66/0a/cbdb2eb4845dafeb632d02a18f47b02f87f2ce4f25266f5e3c017976ce89/uuid_utils-0.10.0.tar.gz", hash = "sha256:5db0e1890e8f008657ffe6ded4d9459af724ab114cfe82af1557c87545301539", size = 18828 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/54/9d22fa16b19e5d1676eba510f08a9c458d96e2a62ff2c8ebad64251afb18/uuid_utils-0.10.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d5a4508feefec62456cd6a41bcdde458d56827d908f226803b886d22a3d5e63", size = 573006 },
{ url = "https://files.pythonhosted.org/packages/08/8e/f895c6e52aa603e521fbc13b8626ba5dd99b6e2f5a55aa96ba5b232f4c53/uuid_utils-0.10.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dbefc2b9113f9dfe56bdae58301a2b3c53792221410d422826f3d1e3e6555fe7", size = 292543 },
{ url = "https://files.pythonhosted.org/packages/b6/58/cc4834f377a5e97d6e184408ad96d13042308de56643b6e24afe1f6f34df/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffc49c33edf87d1ec8112a9b43e4cf55326877716f929c165a2cc307d31c73d5", size = 323340 },
{ url = "https://files.pythonhosted.org/packages/37/e3/6aeddf148f6a7dd7759621b000e8c85382ec83f52ae79b60842d1dc3ab6b/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0636b6208f69d5a4e629707ad2a89a04dfa8d1023e1999181f6830646ca048a1", size = 329653 },
{ url = "https://files.pythonhosted.org/packages/0c/00/dd6c2164ace70b7b1671d9129267df331481d7d1e5f9c5e6a564f07953f6/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc06452856b724df9dedfc161c3582199547da54aeb81915ec2ed54f92d19b0", size = 365471 },
{ url = "https://files.pythonhosted.org/packages/b4/e7/0ab8080fcae5462a7b5e555c1cef3d63457baffb97a59b9bc7b005a3ecb1/uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b2589111c61decdd74a762e8f850c9e4386fb78d2cf7cb4dfc537054cda1b", size = 325844 },
{ url = "https://files.pythonhosted.org/packages/73/39/52d94e9ef75b03f44b39ffc6ac3167e93e74ef4d010a93d25589d9f48540/uuid_utils-0.10.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a558db48b7096de6b4d2d2210d82bba8586a6d55f99106b03bb7d01dc5c5bcd6", size = 344389 },
{ url = "https://files.pythonhosted.org/packages/7c/29/4824566f62666238290d99c62a58e4ab2a8b9cf2eccf94cebd9b3359131e/uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:807465067f3c892514230326ac71a79b28a8dfe2c88ecd2d5675fc844f3c76b5", size = 510078 },
{ url = "https://files.pythonhosted.org/packages/5e/8f/bbcc7130d652462c685f0d3bd26bb214b754215b476340885a4cb50fb89a/uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:57423d4a2b9d7b916de6dbd75ba85465a28f9578a89a97f7d3e098d9aa4e5d4a", size = 515937 },
{ url = "https://files.pythonhosted.org/packages/23/f8/34e0c00f5f188604d336713e6a020fcf53b10998e8ab24735a39ab076740/uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:76d8d660f18ff6b767e319b1b5f927350cd92eafa4831d7ef5b57fdd1d91f974", size = 494111 },
{ url = "https://files.pythonhosted.org/packages/1a/52/b7f0066cc90a7a9c28d54061ed195cd617fde822e5d6ac3ccc88509c3c44/uuid_utils-0.10.0-cp39-abi3-win32.whl", hash = "sha256:6c11a71489338837db0b902b75e1ba7618d5d29f05fde4f68b3f909177dbc226", size = 173520 },
{ url = "https://files.pythonhosted.org/packages/8b/15/f04f58094674d333974243fb45d2c740cf4b79186fb707168e57943c84a3/uuid_utils-0.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:11c55ae64f6c0a7a0c741deae8ca2a4eaa11e9c09dbb7bec2099635696034cf7", size = 182965 },
]
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "6.0.0" version = "6.0.0"