wip
This commit is contained in:
@@ -93,7 +93,7 @@ def _keys(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _ttl_kwargs(ttl: datetime | int | None = None, tz=None):
|
def _ttl_kwargs(ttl: datetime | int | None = None, tz=None) -> dict:
|
||||||
if not ttl:
|
if not ttl:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ def _ttl_kwargs(ttl: datetime | int | None = None, tz=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MissingError(ValueError):
|
class MissingRecordError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ def get_record(
|
|||||||
glom_spec: str | None = None,
|
glom_spec: str | None = None,
|
||||||
raise_on_missing: bool = True,
|
raise_on_missing: bool = True,
|
||||||
default_on_missing: Any = None,
|
default_on_missing: Any = None,
|
||||||
missing_cls: Type[Exception] = MissingError,
|
missing_cls: Type[Exception] = MissingRecordError,
|
||||||
delimiter: str = DELIMITER,
|
delimiter: str = DELIMITER,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ def search(
|
|||||||
r = s.execute()
|
r = s.execute()
|
||||||
except Exception:
|
except Exception:
|
||||||
return {
|
return {
|
||||||
'total_hits': 0,
|
'total_items': 0,
|
||||||
'total_pages': 0,
|
'total_pages': 0,
|
||||||
'hits': [],
|
'items': [],
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'total_hits': r.hits.total.value, # type: ignore
|
'total_items': r.hits.total.value, # type: ignore
|
||||||
'total_pages': math.ceil(r.hits.total.value / page_size), # type: ignore
|
'total_pages': math.ceil(r.hits.total.value / page_size), # type: ignore
|
||||||
'hits': [hit.to_dict() for hit in r],
|
'hits': [hit.to_dict() for hit in r],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class SearchResponse(BaseModel):
|
class SearchResponse(BaseModel):
|
||||||
total_hits: int
|
total_items: int
|
||||||
total_pages: int
|
total_pages: int
|
||||||
hits: list[dict]
|
items: list[dict]
|
||||||
|
|
||||||
|
|
||||||
class RecordResponse(BaseModel):
|
class RecordResponse(BaseModel):
|
||||||
|
|||||||
@@ -1,23 +1,40 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import shortuuid
|
from layercake.extra_types import CnpjStr, CpfStr, NameStr
|
||||||
from layercake.extra_types import CnpjStr
|
from pydantic import (
|
||||||
from pydantic import BaseModel, Field, StringConstraints
|
UUID4,
|
||||||
|
BaseModel,
|
||||||
|
ConfigDict,
|
||||||
|
EmailStr,
|
||||||
|
Field,
|
||||||
|
StringConstraints,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Org(BaseModel):
|
class Org(BaseModel):
|
||||||
id: str
|
id: UUID4 | str = Field(default_factory=uuid4)
|
||||||
name: Annotated[str, StringConstraints(strip_whitespace=True)]
|
name: Annotated[str, StringConstraints(strip_whitespace=True)]
|
||||||
cnpj: CnpjStr | None = None
|
cnpj: CnpjStr | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||||
|
|
||||||
|
id: UUID4 | str = Field(default_factory=uuid4)
|
||||||
|
name: NameStr
|
||||||
|
email: EmailStr
|
||||||
|
email_verified: bool = False
|
||||||
|
cpf: CpfStr | None = None
|
||||||
|
|
||||||
|
|
||||||
class Cert(BaseModel):
|
class Cert(BaseModel):
|
||||||
id: str
|
id: UUID4 | str = Field(default_factory=uuid4)
|
||||||
exp_interval: int
|
exp_interval: int
|
||||||
|
|
||||||
|
|
||||||
class Course(BaseModel):
|
class Course(BaseModel):
|
||||||
id: str = Field(default_factory=shortuuid.uuid)
|
id: UUID4 | str = Field(default_factory=uuid4)
|
||||||
name: str
|
name: str
|
||||||
cert: Cert | None = None
|
cert: Cert | None = None
|
||||||
access_period: int | None = None
|
access_period: int | None = None
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ router = Router()
|
|||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/', compress=True)
|
@router.get('/', compress=True, tags=['Course'])
|
||||||
def get_courses() -> SearchResponse:
|
def get_courses() -> SearchResponse:
|
||||||
event = router.current_event
|
event = router.current_event
|
||||||
query = event.get_query_string_value('query', '{}')
|
query = event.get_query_string_value('query', '{}')
|
||||||
@@ -28,11 +28,11 @@ def get_courses() -> SearchResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post('/', compress=True)
|
@router.post('/', compress=True, tags=['Course'])
|
||||||
def post_course(payload: Course):
|
def post_course(payload: Course):
|
||||||
return Response(status_code=HTTPStatus.CREATED)
|
return Response(status_code=HTTPStatus.CREATED)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/<id>')
|
@router.get('/<id>', compress=True, tags=['Course'])
|
||||||
def get_course(id: str):
|
def get_course(id: str):
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ router = Router()
|
|||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/')
|
@router.get('/', compress=True, tags=['Enrollment'])
|
||||||
def get_enrollments() -> SearchResponse:
|
def get_enrollments() -> SearchResponse:
|
||||||
event = router.current_event
|
event = router.current_event
|
||||||
query = event.get_query_string_value('query', '{}')
|
query = event.get_query_string_value('query', '{}')
|
||||||
@@ -27,7 +27,7 @@ def get_enrollments() -> SearchResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/<id>')
|
@router.get('/<id>', compress=True, tags=['Enrollment'])
|
||||||
def get_enrollment(id: str):
|
def get_enrollment(id: str):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ class CancelPayload(BaseModel):
|
|||||||
status: Literal['CANCELED'] = 'CANCELED'
|
status: Literal['CANCELED'] = 'CANCELED'
|
||||||
|
|
||||||
|
|
||||||
@router.patch('/<id>')
|
@router.patch('/<id>', compress=True, tags=['Enrollment'])
|
||||||
def cancel(id: str, payload: CancelPayload):
|
def cancel(id: str, payload: CancelPayload):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@router.post('/')
|
@router.post('/', compress=True, tags=['Enrollment'])
|
||||||
def enroll():
|
def enroll():
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ router = Router()
|
|||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/')
|
@router.get('/', compress=True, tags=['Order'])
|
||||||
def get_orders() -> SearchResponse:
|
def get_orders() -> SearchResponse:
|
||||||
event = router.current_event
|
event = router.current_event
|
||||||
query = event.get_query_string_value('query', '{}')
|
query = event.get_query_string_value('query', '{}')
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ from aws_lambda_powertools.event_handler.api_gateway import (
|
|||||||
Router,
|
Router,
|
||||||
)
|
)
|
||||||
from elasticsearch import Elasticsearch
|
from elasticsearch import Elasticsearch
|
||||||
from pydantic import UUID4, BaseModel, StringConstraints
|
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||||
from dynamodb import KeyLoc, get_records
|
from pydantic import UUID4, BaseModel, StringConstraints
|
||||||
|
|
||||||
import elastic
|
import elastic
|
||||||
|
from dynamodb import KeyLoc, get_records
|
||||||
from http_models import RecordResponse, SearchResponse
|
from http_models import RecordResponse, SearchResponse
|
||||||
|
from models import User
|
||||||
from settings import ELASTIC_CONN, USER_TABLE
|
from settings import ELASTIC_CONN, USER_TABLE
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
@@ -36,6 +37,11 @@ def get_users() -> SearchResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/', compress=True, tags=['User'], summary='Create user')
|
||||||
|
def post_user(payload: User):
|
||||||
|
return Response(status_code=HTTPStatus.CREATED)
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordPayload(BaseModel):
|
class ResetPasswordPayload(BaseModel):
|
||||||
cognito_sub: UUID4
|
cognito_sub: UUID4
|
||||||
new_password: Annotated[str, StringConstraints(min_length=6)]
|
new_password: Annotated[str, StringConstraints(min_length=6)]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Globals:
|
|||||||
Architectures:
|
Architectures:
|
||||||
- x86_64
|
- x86_64
|
||||||
Layers:
|
Layers:
|
||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:10
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:13
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ import base64
|
|||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from http import HTTPMethod
|
from http import HTTPMethod
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import boto3
|
||||||
import pytest
|
import pytest
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||||
|
|
||||||
|
table_name = 'pytest'
|
||||||
|
dynamodb_endpoint_url = 'http://127.0.0.1:8000'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -86,6 +92,36 @@ def http_api_proxy():
|
|||||||
return HttpApiProxy()
|
return HttpApiProxy()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dynamodb_client():
|
||||||
|
return boto3.client('dynamodb', endpoint_url=dynamodb_endpoint_url)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def dynamodb_persistence_layer(
|
||||||
|
dynamodb_client,
|
||||||
|
) -> Generator[DynamoDBPersistenceLayer, None, None]:
|
||||||
|
dynamodb_client.create_table(
|
||||||
|
AttributeDefinitions=[
|
||||||
|
{'AttributeName': 'id', 'AttributeType': 'S'},
|
||||||
|
{'AttributeName': 'sk', 'AttributeType': 'S'},
|
||||||
|
],
|
||||||
|
TableName=table_name,
|
||||||
|
KeySchema=[
|
||||||
|
{'AttributeName': 'id', 'KeyType': 'HASH'},
|
||||||
|
{'AttributeName': 'sk', 'KeyType': 'RANGE'},
|
||||||
|
],
|
||||||
|
ProvisionedThroughput={
|
||||||
|
'ReadCapacityUnits': 123,
|
||||||
|
'WriteCapacityUnits': 123,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
yield DynamoDBPersistenceLayer(table_name, dynamodb_client)
|
||||||
|
|
||||||
|
dynamodb_client.delete_table(TableName=table_name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_app(monkeypatch):
|
def mock_app(monkeypatch):
|
||||||
monkeypatch.setattr('settings.ELASTIC_CONN', {'hosts': 'http://127.0.0.1:9200'})
|
monkeypatch.setattr('settings.ELASTIC_CONN', {'hosts': 'http://127.0.0.1:9200'})
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ from http import HTTPMethod, HTTPStatus
|
|||||||
from ..conftest import HttpApiProxy, LambdaContext
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
def test_courses(mock_app, http_api_proxy: HttpApiProxy, lambda_context: LambdaContext):
|
def test_post_course(
|
||||||
|
mock_app,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
r = mock_app.lambda_handler(
|
r = mock_app.lambda_handler(
|
||||||
http_api_proxy(
|
http_api_proxy(
|
||||||
raw_path='/courses',
|
raw_path='/courses',
|
||||||
|
|||||||
24
http-api/tests/routes/test_users.py
Normal file
24
http-api/tests/routes/test_users.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from http import HTTPMethod, HTTPStatus
|
||||||
|
|
||||||
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_user(
|
||||||
|
mock_app,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
r = mock_app.lambda_handler(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/users',
|
||||||
|
method=HTTPMethod.POST,
|
||||||
|
body={
|
||||||
|
'name': 'Sérgio R Siqueira',
|
||||||
|
'email': 'sergio@somosbeta.com.br',
|
||||||
|
'cpf': '07879819908',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert r['statusCode'] == HTTPStatus.CREATED
|
||||||
40
http-api/uv.lock
generated
40
http-api/uv.lock
generated
@@ -232,6 +232,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 },
|
{ url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dnspython"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elastic-transport"
|
name = "elastic-transport"
|
||||||
version = "8.17.1"
|
version = "8.17.1"
|
||||||
@@ -272,6 +281,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ad/b4/5e707bca39062ba0b5227696a767db09767e5f09e869c6cb14aeb36e4b9d/elasticsearch_dsl-8.17.1-py3-none-any.whl", hash = "sha256:49ee12a6a8d43fcfc0af42b49649531a6ef228c9e4795325de27f6b309b62b6d", size = 158294 },
|
{ url = "https://files.pythonhosted.org/packages/ad/b4/5e707bca39062ba0b5227696a767db09767e5f09e869c6cb14aeb36e4b9d/elasticsearch_dsl-8.17.1-py3-none-any.whl", hash = "sha256:49ee12a6a8d43fcfc0af42b49649531a6ef228c9e4795325de27f6b309b62b6d", size = 158294 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email-validator"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "dnspython" },
|
||||||
|
{ name = "idna" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "face"
|
name = "face"
|
||||||
version = "24.0.0"
|
version = "24.0.0"
|
||||||
@@ -344,6 +366,15 @@ dev = [
|
|||||||
{ name = "ruff", specifier = ">=0.9.1" },
|
{ name = "ruff", specifier = ">=0.9.1" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -387,7 +418,7 @@ dependencies = [
|
|||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "shortuuid" },
|
{ name = "shortuuid" },
|
||||||
@@ -403,7 +434,7 @@ requires-dist = [
|
|||||||
{ name = "glom", specifier = ">=24.11.0" },
|
{ name = "glom", specifier = ">=24.11.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
||||||
{ name = "pydantic", specifier = ">=2.10.6" },
|
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
||||||
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
||||||
{ name = "pytz", specifier = ">=2025.1" },
|
{ name = "pytz", specifier = ">=2025.1" },
|
||||||
{ name = "shortuuid", specifier = ">=1.0.13" },
|
{ name = "shortuuid", specifier = ">=1.0.13" },
|
||||||
@@ -509,6 +540,11 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
email = [
|
||||||
|
{ name = "email-validator" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-core"
|
name = "pydantic-core"
|
||||||
version = "2.27.2"
|
version = "2.27.2"
|
||||||
|
|||||||
@@ -9,31 +9,49 @@ from botocore.exceptions import ClientError
|
|||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _serialize(v):
|
def _serialize_python_types(obj: Any) -> str | dict | list:
|
||||||
if isinstance(v, datetime):
|
match obj:
|
||||||
return v.isoformat()
|
case datetime():
|
||||||
|
return obj.isoformat()
|
||||||
if isinstance(v, IPv4Address):
|
case IPv4Address():
|
||||||
return str(v)
|
return str(obj)
|
||||||
|
case list() | tuple():
|
||||||
if isinstance(v, (list, tuple)):
|
return [_serialize_python_types(v) for v in obj]
|
||||||
return [_serialize(x) for x in v]
|
case dict():
|
||||||
|
return {k: _serialize_python_types(v) for k, v in obj.items()}
|
||||||
if isinstance(v, dict):
|
case _:
|
||||||
return {k: _serialize(dv) for k, dv in v.items()}
|
return obj
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
def serialize(obj: dict) -> dict:
|
def serialize(obj: dict) -> dict:
|
||||||
return {k: TypeSerializer().serialize(_serialize(v)) for k, v in obj.items()}
|
serializer = TypeSerializer()
|
||||||
|
return {k: serializer.serialize(_serialize_python_types(v)) for k, v in obj.items()}
|
||||||
|
|
||||||
|
|
||||||
def deserialize(obj: dict) -> dict:
|
def deserialize(obj: dict) -> dict:
|
||||||
return {k: TypeDeserializer().deserialize(v) for k, v in obj.items()}
|
deserializer = TypeDeserializer()
|
||||||
|
return {k: deserializer.deserialize(v) for k, v in obj.items()}
|
||||||
|
|
||||||
|
|
||||||
def Key(pk: str, sk: str) -> dict[str, str]:
|
def Key(
|
||||||
|
val: str | tuple[str, ...],
|
||||||
|
*,
|
||||||
|
prefix: str | None = None,
|
||||||
|
delimiter: str = '#',
|
||||||
|
) -> str:
|
||||||
|
if not prefix and not isinstance(val, tuple):
|
||||||
|
return val
|
||||||
|
|
||||||
|
if isinstance(val, str):
|
||||||
|
val = (val,)
|
||||||
|
|
||||||
|
if prefix:
|
||||||
|
val = (prefix,) + val
|
||||||
|
|
||||||
|
return delimiter.join(val)
|
||||||
|
|
||||||
|
|
||||||
|
def KeyPair(pk: str, sk: str) -> dict[str, str]:
|
||||||
return {
|
return {
|
||||||
'id': pk,
|
'id': pk,
|
||||||
'sk': sk,
|
'sk': sk,
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from typing import TYPE_CHECKING, Annotated, Any
|
|||||||
|
|
||||||
import ftfy
|
import ftfy
|
||||||
from pycpfcnpj import cpfcnpj
|
from pycpfcnpj import cpfcnpj
|
||||||
from pydantic import BaseModel, Field, GetCoreSchemaHandler
|
from pydantic import BaseModel, Field, GetCoreSchemaHandler, GetJsonSchemaHandler
|
||||||
|
from pydantic.json_schema import JsonSchemaValue
|
||||||
from pydantic_core import CoreSchema, core_schema
|
from pydantic_core import CoreSchema, core_schema
|
||||||
from pydantic_extra_types.payment import PaymentCardNumber
|
from pydantic_extra_types.payment import PaymentCardNumber
|
||||||
|
|
||||||
@@ -47,6 +48,14 @@ else:
|
|||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __get_pydantic_json_schema__(
|
||||||
|
cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
|
||||||
|
) -> JsonSchemaValue:
|
||||||
|
field_schema = handler(core_schema)
|
||||||
|
field_schema.update(type='string', format='name')
|
||||||
|
return field_schema
|
||||||
|
|
||||||
|
|
||||||
class PaymentCardValidation:
|
class PaymentCardValidation:
|
||||||
"""
|
"""
|
||||||
@@ -145,11 +154,9 @@ if TYPE_CHECKING:
|
|||||||
CnpjStr = Annotated[str, ...]
|
CnpjStr = Annotated[str, ...]
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class CpfStr(CpfCnpj):
|
class CpfStr(CpfCnpj): ...
|
||||||
...
|
|
||||||
|
|
||||||
class CnpjStr(CpfCnpj):
|
class CnpjStr(CpfCnpj): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
0
layercake/layercake/py.typed
Normal file
0
layercake/layercake/py.typed
Normal file
@@ -16,7 +16,7 @@ dependencies = [
|
|||||||
"glom>=24.11.0",
|
"glom>=24.11.0",
|
||||||
"orjson>=3.10.15",
|
"orjson>=3.10.15",
|
||||||
"pycpfcnpj>=1.8",
|
"pycpfcnpj>=1.8",
|
||||||
"pydantic>=2.10.6",
|
"pydantic[email]>=2.10.6",
|
||||||
"pydantic-extra-types>=2.10.3",
|
"pydantic-extra-types>=2.10.3",
|
||||||
"pytz>=2025.1",
|
"pytz>=2025.1",
|
||||||
"shortuuid>=1.0.13",
|
"shortuuid>=1.0.13",
|
||||||
|
|||||||
@@ -1,7 +1,40 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, TransactItems
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
Key,
|
||||||
|
KeyPair,
|
||||||
|
TransactItems,
|
||||||
|
serialize,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_serialize():
|
||||||
|
assert serialize(
|
||||||
|
{
|
||||||
|
'id': '123',
|
||||||
|
'sk': 'abc',
|
||||||
|
'date': datetime.fromisoformat('2025-03-20T18:29:10.713994'),
|
||||||
|
'ip': IPv4Address('127.0.0.1'),
|
||||||
|
}
|
||||||
|
) == {
|
||||||
|
'id': {'S': '123'},
|
||||||
|
'sk': {'S': 'abc'},
|
||||||
|
'date': {'S': '2025-03-20T18:29:10.713994'},
|
||||||
|
'ip': {'S': '127.0.0.1'},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_key():
|
||||||
|
assert Key(('122', 'abc'), prefix='schedules') == 'schedules#122#abc'
|
||||||
|
|
||||||
|
|
||||||
|
def test_keypair():
|
||||||
|
assert KeyPair('123', 'abc') == {'id': '123', 'sk': 'abc'}
|
||||||
|
|
||||||
|
|
||||||
def test_transact_write_items(dynamodb_client):
|
def test_transact_write_items(dynamodb_client):
|
||||||
@@ -33,5 +66,6 @@ def test_transact_write_items(dynamodb_client):
|
|||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ClientError):
|
with pytest.raises(ClientError):
|
||||||
user_layer.transact_write_items(transact)
|
user_layer.transact_write_items(transact)
|
||||||
|
|||||||
21
layercake/tests/test_funcs.py
Normal file
21
layercake/tests/test_funcs.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from layercake.funcs import omit, pick
|
||||||
|
|
||||||
|
|
||||||
|
def test_omit():
|
||||||
|
values = {'indigo': '#4b0082', 'navy': '#000080'}
|
||||||
|
assert omit(['indigo'], values) == {'navy': '#000080'}
|
||||||
|
assert omit(['test'], values) == values
|
||||||
|
|
||||||
|
|
||||||
|
def test_pick():
|
||||||
|
values = {'indigo': '#4b0082', 'navy': '#000080'}
|
||||||
|
assert pick(['navy'], values) == {'navy': '#000080'}
|
||||||
|
assert pick(['test'], values) == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_pick_default_val():
|
||||||
|
values = {'name': 'test'}
|
||||||
|
assert pick(['name', 'surname'], values, exclude_none=False, default=False) == {
|
||||||
|
'name': 'test',
|
||||||
|
'surname': False,
|
||||||
|
}
|
||||||
40
layercake/uv.lock
generated
40
layercake/uv.lock
generated
@@ -232,6 +232,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 },
|
{ url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dnspython"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elastic-transport"
|
name = "elastic-transport"
|
||||||
version = "8.17.1"
|
version = "8.17.1"
|
||||||
@@ -272,6 +281,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ad/b4/5e707bca39062ba0b5227696a767db09767e5f09e869c6cb14aeb36e4b9d/elasticsearch_dsl-8.17.1-py3-none-any.whl", hash = "sha256:49ee12a6a8d43fcfc0af42b49649531a6ef228c9e4795325de27f6b309b62b6d", size = 158294 },
|
{ url = "https://files.pythonhosted.org/packages/ad/b4/5e707bca39062ba0b5227696a767db09767e5f09e869c6cb14aeb36e4b9d/elasticsearch_dsl-8.17.1-py3-none-any.whl", hash = "sha256:49ee12a6a8d43fcfc0af42b49649531a6ef228c9e4795325de27f6b309b62b6d", size = 158294 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email-validator"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "dnspython" },
|
||||||
|
{ name = "idna" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "face"
|
name = "face"
|
||||||
version = "24.0.0"
|
version = "24.0.0"
|
||||||
@@ -319,6 +341,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/9c/a2/75fd80784ec33da8d39cf885e8811a4fbc045a90db5e336b8e345e66dbb2/glom-24.11.0-py3-none-any.whl", hash = "sha256:991db7fcb4bfa9687010aa519b7b541bbe21111e70e58fdd2d7e34bbaa2c1fbd", size = 102690 },
|
{ url = "https://files.pythonhosted.org/packages/9c/a2/75fd80784ec33da8d39cf885e8811a4fbc045a90db5e336b8e345e66dbb2/glom-24.11.0-py3-none-any.whl", hash = "sha256:991db7fcb4bfa9687010aa519b7b541bbe21111e70e58fdd2d7e34bbaa2c1fbd", size = 102690 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -362,7 +393,7 @@ dependencies = [
|
|||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "shortuuid" },
|
{ name = "shortuuid" },
|
||||||
@@ -385,7 +416,7 @@ requires-dist = [
|
|||||||
{ name = "glom", specifier = ">=24.11.0" },
|
{ name = "glom", specifier = ">=24.11.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
||||||
{ name = "pydantic", specifier = ">=2.10.6" },
|
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
||||||
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
||||||
{ name = "pytz", specifier = ">=2025.1" },
|
{ name = "pytz", specifier = ">=2025.1" },
|
||||||
{ name = "shortuuid", specifier = ">=1.0.13" },
|
{ name = "shortuuid", specifier = ">=1.0.13" },
|
||||||
@@ -491,6 +522,11 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
email = [
|
||||||
|
{ name = "email-validator" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-core"
|
name = "pydantic-core"
|
||||||
version = "2.27.2"
|
version = "2.27.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user