add reset password
This commit is contained in:
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -29,14 +29,14 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
action : str
|
action: str
|
||||||
The identifier for the audit log action.
|
The identifier for the audit log action.
|
||||||
collect : DynamoDBCollection
|
collect: DynamoDBCollection
|
||||||
The collection instance used to persist the audit log data.
|
The collection instance used to persist the audit log data.
|
||||||
audit_attrs : tuple of str, optional
|
audit_attrs: tuple of str, optional
|
||||||
A tuple of attribute names to extract from the response body for logging.
|
A tuple of attribute names to extract from the response body for logging.
|
||||||
These represent the specific fields to include in the audit log.
|
These represent the specific fields to include in the audit log.
|
||||||
retention_days : int or None, optional
|
retention_days: int or None, optional
|
||||||
The number of days the log is retained on the server.
|
The number of days the log is retained on the server.
|
||||||
If None, no time-to-live (TTL) will be applied.
|
If None, no time-to-live (TTL) will be applied.
|
||||||
"""
|
"""
|
||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
143
layercake/uv.lock
generated
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user