ci: add Dockerfile

This commit is contained in:
cătălin 2023-12-09 11:15:39 +01:00
commit 65a56f2658
Signed by: catalin
GPG key ID: 0178DF42F43E5FD2
15 changed files with 382 additions and 70 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ apps/http/sls/.serverless/
apps/http/sls/.requirements.zip apps/http/sls/.requirements.zip
__pycache__/ __pycache__/
.pdm-python .pdm-python
.pdm-build/
.env .env
.coverage .coverage
htmlcov htmlcov

41
Dockerfile Normal file
View file

@ -0,0 +1,41 @@
FROM python:3.11-slim AS base
ENV PYTHONDONTWRITEBYTECODE 1
ENV USERNAME "secretsanta"
ENV APP_HOME "/home/$USERNAME"
ENV APP_PATH "$APP_HOME/src"
ARG uid=1000
ARG gid=1000
# hadolint ignore=DL3001,DL3008
RUN apt-get -y update \
&& apt-get -y upgrade \
&& apt-get -y install git curl make python3-virtualenv --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd -g "$gid" "$USERNAME" \
&& useradd -lu "$uid" -g "$gid" "$USERNAME" \
&& mkhomedir_helper "$USERNAME" \
&& mkdir "$APP_PATH" \
&& chown "$uid:$gid" "$APP_PATH"
USER "$USERNAME"
ENV PATH="${PATH}:$APP_HOME/.local/bin"
ENV PYTHONPATH "$APP_PATH"
RUN pip install pipx==1.2.0 --no-cache-dir \
&& pipx install pdm==2.10
WORKDIR "$APP_PATH"
ENV POETRY_VIRTUALENVS_IN_PROJECT=1
COPY --chown="$USERNAME" pyproject.toml pdm.lock Makefile README.md LICENSE ./
COPY --chown="$USERNAME" apps/ apps/
COPY --chown="$USERNAME" secretsanta/ secretsanta/
RUN pdm install
FROM base AS run
RUN pdm install -G litestar
WORKDIR "$APP_PATH/apps/http/litestar"
CMD ["pdm", "run", "litestar", "run"]

View file

@ -1,7 +1,14 @@
build:
docker build --target run -t git.roboces.dev/catalin/secretsanta:latest .
run--sls: run--sls:
@echo "Activating virtual environment and starting serverless offline" @echo "Activating virtual environment and starting serverless offline"
@. .venv/bin/activate && cd apps/http/sls && npx sls offline @. .venv/bin/activate && cd apps/http/sls && npx sls offline
run--litestar:
@echo "Starting litestar"
@. cd apps/http/litestar/ && pdm run litestar run
deploy--sls: deploy--sls:
@echo "Deploying via serverless framework" @echo "Deploying via serverless framework"
@. cd apps/http/sls && pdm export -G sls -f requirements --prod > requirements.txt && npx sls deploy @. cd apps/http/sls && pdm export -G sls -f requirements --prod > requirements.txt && npx sls deploy

View file

@ -1,8 +0,0 @@
{
"version": "2.0",
"app_name": "secretsanta",
"stages": {
"dev": {
}
}
}

View file

@ -4,8 +4,8 @@ from chalice import Chalice
from secretsanta.domain import Group from secretsanta.domain import Group
from secretsanta.repo import S3Repo from secretsanta.repo import S3Repo
from secretsanta.service import ( from secretsanta.service import (
GetGroupParticipantsService,
CreateGroupService, CreateGroupService,
GetGroupParticipantsService,
GetPairService, GetPairService,
) )
from secretsanta.settings import get_config from secretsanta.settings import get_config
@ -16,13 +16,6 @@ app = Chalice(app_name="secretsanta", debug=config.environment == "local")
repo = S3Repo() repo = S3Repo()
def _participants2url(participants: list[str], group_uuid: str) -> list[str]:
return [
f"{config.server_url}/api/v1/groups/{group_uuid}/pair/{participant_name}"
for participant_name in participants
]
@app.route("/api/v1/groups/{group_uuid}") @app.route("/api/v1/groups/{group_uuid}")
def get_participants(group_uuid: str): def get_participants(group_uuid: str):
service = GetGroupParticipantsService(repo) service = GetGroupParticipantsService(repo)
@ -34,8 +27,7 @@ def create_group(group_uuid: str):
request = app.current_request request = app.current_request
body = request.json_body body = request.json_body
service = CreateGroupService(repo) service = CreateGroupService(repo)
participants = anyio.run(service.run, Group(uuid=group_uuid, **body)).participants return anyio.run(service.run, Group(uuid=group_uuid, **body))
return _participants2url(participants, group_uuid)
@app.route("/api/v1/groups/{group_uuid}/pair/{participant}") @app.route("/api/v1/groups/{group_uuid}/pair/{participant}")

View file

@ -1,15 +1,15 @@
from dataclasses import dataclass, asdict from dataclasses import asdict, dataclass
from typing import Literal, Annotated from typing import Annotated, Literal
from litestar import Controller, get, Litestar, put, Request, Response, MediaType from litestar import Controller, Litestar, MediaType, Request, Response, get, put
from litestar.params import Parameter from litestar.params import Parameter
from pydantic import UUID4 from pydantic import UUID4
from secretsanta.domain import Group from secretsanta.domain import Group
from secretsanta.repo import S3Repo from secretsanta.repo import S3Repo
from secretsanta.service import ( from secretsanta.service import (
GetGroupParticipantsService,
CreateGroupService, CreateGroupService,
GetGroupParticipantsService,
GetPairService, GetPairService,
) )
from secretsanta.settings import get_config from secretsanta.settings import get_config
@ -46,10 +46,13 @@ class GroupController(Controller):
@put("/{group_uuid:uuid}") @put("/{group_uuid:uuid}")
async def create_group( async def create_group(
self, request: Request, group_uuid: UUID4, data: CreateGroup self,
request: Request,
group_uuid: UUID4,
data: CreateGroup,
) -> list[str]: ) -> list[str]:
group = await self.create_group_service.run( group = await self.create_group_service.run(
Group(uuid=group_uuid, **asdict(data)) Group(uuid=group_uuid, **asdict(data)),
) )
return self._group2urls(group.participants, request.url) return self._group2urls(group.participants, request.url)
@ -59,11 +62,13 @@ class GroupController(Controller):
group_uuid: UUID4, group_uuid: UUID4,
participant_name: str, participant_name: str,
response_type: Annotated[ response_type: Annotated[
Literal["json", "plain"], Parameter(query="type") Literal["json", "plain"],
Parameter(query="type"),
] = "plain", ] = "plain",
) -> Response[str] | PairResponse: ) -> Response[str] | PairResponse:
pair = await self.get_pair_service.run( pair = await self.get_pair_service.run(
group_uuid=group_uuid, participant=participant_name group_uuid=group_uuid,
participant=participant_name,
) )
if response_type == "json": if response_type == "json":
return PairResponse(pair=pair) return PairResponse(pair=pair)

View file

@ -5,9 +5,9 @@ import anyio
from secretsanta.domain import Group from secretsanta.domain import Group
from secretsanta.repo import S3Repo from secretsanta.repo import S3Repo
from secretsanta.service import ( from secretsanta.service import (
CreateGroupService,
GetGroupParticipantsService, GetGroupParticipantsService,
GetPairService, GetPairService,
CreateGroupService,
) )
from secretsanta.settings import get_config from secretsanta.settings import get_config

View file

@ -26,16 +26,16 @@ custom:
dockerizePip: true dockerizePip: true
zip: true zip: true
slim: true slim: true
layer: true layer: false
prune: #prune:
automatic: true # automatic: true
includeLayers: true # includeLayers: true
number: 1 # number: 1
apiGatewayCaching: #apiGatewayCaching:
enabled: true # enabled: true
apiGatewayThrottling: #apiGatewayThrottling:
maxRequestsPerSecond: 100 # maxRequestsPerSecond: 100
maxConcurrentRequests: 5 # maxConcurrentRequests: 5
plugins: plugins:
- serverless-python-requirements - serverless-python-requirements
@ -47,27 +47,25 @@ plugins:
functions: functions:
get_participants: get_participants:
handler: app.get_participants handler: app.get_participants
layers:
- Ref: PythonRequirementsLambdaLayer
events: events:
- httpApi: - httpApi:
path: /api/v1/groups/{group_uuid} path: /api/v1/groups/{group_uuid}
method: get method: get
create_group: #create_group:
handler: app.create_group # handler: app.create_group
layers: # layers:
- Ref: PythonRequirementsLambdaLayer # - Ref: PythonRequirementsLambdaLayer
events: # events:
- httpApi: # - httpApi:
path: /api/v1/groups/{group_uuid} # path: /api/v1/groups/{group_uuid}
method: put # method: put
get_pair: #get_pair:
handler: app.get_pair # handler: app.get_pair
layers: # layers:
- Ref: PythonRequirementsLambdaLayer # - Ref: PythonRequirementsLambdaLayer
events: # events:
- httpApi: # - httpApi:
path: /api/v1/groups/{group_uuid}/pair/{participant} # path: /api/v1/groups/{group_uuid}/pair/{participant}
method: get # method: get

262
pdm.lock generated
View file

@ -5,7 +5,7 @@
groups = ["default", "litestar", "testing", "dev", "linting", "chalice", "sls"] groups = ["default", "litestar", "testing", "dev", "linting", "chalice", "sls"]
strategy = ["cross_platform"] strategy = ["cross_platform"]
lock_version = "4.4" lock_version = "4.4"
content_hash = "sha256:3047d1e2fc131623e2ccfaab7af28560d897f225b61904fed245930b74f43c61" content_hash = "sha256:ba9863870aa9f5f47435713bc9ae20992b801a13bced3f58aad28f2a5685614d"
[[package]] [[package]]
name = "aioboto3" name = "aioboto3"
@ -193,6 +193,36 @@ files = [
{file = "boto3-1.28.64.tar.gz", hash = "sha256:a5cf93b202568e9d378afdc84be55a6dedf11d30156289fe829e23e6d7dccabb"}, {file = "boto3-1.28.64.tar.gz", hash = "sha256:a5cf93b202568e9d378afdc84be55a6dedf11d30156289fe829e23e6d7dccabb"},
] ]
[[package]]
name = "boto3-stubs"
version = "1.33.10"
requires_python = ">=3.7"
summary = "Type annotations for boto3 1.33.10 generated with mypy-boto3-builder 7.21.0"
dependencies = [
"botocore-stubs",
"types-s3transfer",
"typing-extensions>=4.1.0; python_version < \"3.12\"",
]
files = [
{file = "boto3-stubs-1.33.10.tar.gz", hash = "sha256:9e29024bcb6ac12c220d8ea5bfe79c532c92e0c1d1170c14217165e1a8b09218"},
{file = "boto3_stubs-1.33.10-py3-none-any.whl", hash = "sha256:61d07f445f88d5cca0a66e23dfd83611ff947bac08e01a64908df3605c22aabc"},
]
[[package]]
name = "boto3-stubs"
version = "1.33.10"
extras = ["s3"]
requires_python = ">=3.7"
summary = "Type annotations for boto3 1.33.10 generated with mypy-boto3-builder 7.21.0"
dependencies = [
"boto3-stubs==1.33.10",
"mypy-boto3-s3<1.34.0,>=1.33.0",
]
files = [
{file = "boto3-stubs-1.33.10.tar.gz", hash = "sha256:9e29024bcb6ac12c220d8ea5bfe79c532c92e0c1d1170c14217165e1a8b09218"},
{file = "boto3_stubs-1.33.10-py3-none-any.whl", hash = "sha256:61d07f445f88d5cca0a66e23dfd83611ff947bac08e01a64908df3605c22aabc"},
]
[[package]] [[package]]
name = "botocore" name = "botocore"
version = "1.31.64" version = "1.31.64"
@ -231,6 +261,39 @@ files = [
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
] ]
[[package]]
name = "cffi"
version = "1.16.0"
requires_python = ">=3.8"
summary = "Foreign Function Interface for Python calling C code."
dependencies = [
"pycparser",
]
files = [
{file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
{file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
{file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
{file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
{file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
{file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
{file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
{file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
{file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
]
[[package]] [[package]]
name = "chalice" name = "chalice"
version = "1.29.0" version = "1.29.0"
@ -252,6 +315,46 @@ files = [
{file = "chalice-1.29.0.tar.gz", hash = "sha256:f07aad9a2d4e5b06ef279cf668a48ec455b131168000b91efe7d841425599035"}, {file = "chalice-1.29.0.tar.gz", hash = "sha256:f07aad9a2d4e5b06ef279cf668a48ec455b131168000b91efe7d841425599035"},
] ]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
requires_python = ">=3.7.0"
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.7" version = "8.1.7"
@ -339,6 +442,40 @@ files = [
{file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"},
] ]
[[package]]
name = "cryptography"
version = "41.0.7"
requires_python = ">=3.7"
summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
dependencies = [
"cffi>=1.12",
]
files = [
{file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"},
{file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"},
{file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"},
{file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"},
{file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"},
{file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"},
{file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"},
{file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"},
{file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"},
{file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"},
{file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"},
{file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"},
{file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"},
{file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"},
{file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"},
{file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"},
{file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"},
{file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"},
{file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"},
{file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"},
{file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"},
{file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"},
{file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"},
]
[[package]] [[package]]
name = "editorconfig" name = "editorconfig"
version = "0.12.3" version = "0.12.3"
@ -648,6 +785,43 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
] ]
[[package]]
name = "moto"
version = "4.2.11"
requires_python = ">=3.7"
summary = ""
dependencies = [
"Jinja2>=2.10.1",
"boto3>=1.9.201",
"botocore>=1.12.201",
"cryptography>=3.3.1",
"python-dateutil<3.0.0,>=2.1",
"requests>=2.5",
"responses>=0.13.0",
"werkzeug!=2.2.0,!=2.2.1,>=0.5",
"xmltodict",
]
files = [
{file = "moto-4.2.11-py2.py3-none-any.whl", hash = "sha256:58c12ab9ee69b6a5d1cddf83611ba4071508f07894317c57844b3ae6dc5bcd38"},
{file = "moto-4.2.11.tar.gz", hash = "sha256:2da62d52eaa765dfe2762c920f0a88a58f3a09e04581c91db967d92faec848f1"},
]
[[package]]
name = "moto"
version = "4.2.11"
extras = ["s3"]
requires_python = ">=3.7"
summary = ""
dependencies = [
"PyYAML>=5.1",
"moto==4.2.11",
"py-partiql-parser==0.4.2",
]
files = [
{file = "moto-4.2.11-py2.py3-none-any.whl", hash = "sha256:58c12ab9ee69b6a5d1cddf83611ba4071508f07894317c57844b3ae6dc5bcd38"},
{file = "moto-4.2.11.tar.gz", hash = "sha256:2da62d52eaa765dfe2762c920f0a88a58f3a09e04581c91db967d92faec848f1"},
]
[[package]] [[package]]
name = "msgspec" name = "msgspec"
version = "0.18.4" version = "0.18.4"
@ -695,6 +869,19 @@ files = [
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
] ]
[[package]]
name = "mypy-boto3-s3"
version = "1.33.2"
requires_python = ">=3.7"
summary = "Type annotations for boto3.S3 1.33.2 service generated with mypy-boto3-builder 7.20.3"
dependencies = [
"typing-extensions>=4.1.0; python_version < \"3.12\"",
]
files = [
{file = "mypy-boto3-s3-1.33.2.tar.gz", hash = "sha256:f54a3ad3288f4e4719ebada3dde68c320507b0fc451d59bc68af7e6ab15cbdad"},
{file = "mypy_boto3_s3-1.33.2-py3-none-any.whl", hash = "sha256:9d463df6def30de31a467d49ab92ff7795d46709d56eff6f52216a08bac27918"},
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "23.2" version = "23.2"
@ -739,6 +926,25 @@ files = [
{file = "polyfactory-2.12.0.tar.gz", hash = "sha256:26dc3a52baae1ebd6386708d9a99f8ea4ef57c9d45e556815ee5e44a1cd27fc0"}, {file = "polyfactory-2.12.0.tar.gz", hash = "sha256:26dc3a52baae1ebd6386708d9a99f8ea4ef57c9d45e556815ee5e44a1cd27fc0"},
] ]
[[package]]
name = "py-partiql-parser"
version = "0.4.2"
summary = "Pure Python PartiQL Parser"
files = [
{file = "py-partiql-parser-0.4.2.tar.gz", hash = "sha256:9c99d545be7897c6bfa97a107f6cfbcd92e359d394e4f3b95430e6409e8dd1e1"},
{file = "py_partiql_parser-0.4.2-py3-none-any.whl", hash = "sha256:f3f34de8dddf65ed2d47b4263560bbf97be1ecc6bd5c61da039ede90f26a10ce"},
]
[[package]]
name = "pycparser"
version = "2.21"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
summary = "C parser in Python"
files = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.5.2" version = "2.5.2"
@ -944,6 +1150,37 @@ files = [
{file = "readchar-4.0.5.tar.gz", hash = "sha256:08a456c2d7c1888cde3f4688b542621b676eb38cd6cfed7eb6cb2e2905ddc826"}, {file = "readchar-4.0.5.tar.gz", hash = "sha256:08a456c2d7c1888cde3f4688b542621b676eb38cd6cfed7eb6cb2e2905ddc826"},
] ]
[[package]]
name = "requests"
version = "2.31.0"
requires_python = ">=3.7"
summary = "Python HTTP for Humans."
dependencies = [
"certifi>=2017.4.17",
"charset-normalizer<4,>=2",
"idna<4,>=2.5",
"urllib3<3,>=1.21.1",
]
files = [
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
]
[[package]]
name = "responses"
version = "0.24.1"
requires_python = ">=3.8"
summary = "A utility library for mocking out the `requests` Python library."
dependencies = [
"pyyaml",
"requests<3.0,>=2.30.0",
"urllib3<3.0,>=1.25.10",
]
files = [
{file = "responses-0.24.1-py3-none-any.whl", hash = "sha256:a2b43f4c08bfb9c9bd242568328c65a34b318741d3fab884ac843c5ceeb543f9"},
{file = "responses-0.24.1.tar.gz", hash = "sha256:b127c6ca3f8df0eb9cc82fd93109a3007a86acb24871834c47b77765152ecf8c"},
]
[[package]] [[package]]
name = "rich" name = "rich"
version = "13.7.0" version = "13.7.0"
@ -1301,6 +1538,19 @@ files = [
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
] ]
[[package]]
name = "werkzeug"
version = "3.0.1"
requires_python = ">=3.8"
summary = "The comprehensive WSGI web application library."
dependencies = [
"MarkupSafe>=2.1.1",
]
files = [
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
]
[[package]] [[package]]
name = "wheel" name = "wheel"
version = "0.42.0" version = "0.42.0"
@ -1341,6 +1591,16 @@ files = [
{file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"},
] ]
[[package]]
name = "xmltodict"
version = "0.13.0"
requires_python = ">=3.4"
summary = "Makes working with XML feel like you are working with JSON"
files = [
{file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"},
{file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"},
]
[[package]] [[package]]
name = "yarl" name = "yarl"
version = "1.9.4" version = "1.9.4"

View file

@ -22,6 +22,7 @@ testing = [
"polyfactory>=2.12.0", "polyfactory>=2.12.0",
"pytest>=7.4.3", "pytest>=7.4.3",
"pytest-cov>=4.1.0", "pytest-cov>=4.1.0",
"moto[s3]>=4.2.11",
] ]
linting = [ linting = [
"ruff>=0.1.7", "ruff>=0.1.7",
@ -41,4 +42,9 @@ build-backend = "pdm.backend"
[tool.pdm.dev-dependencies] [tool.pdm.dev-dependencies]
dev = [ dev = [
"types-aioboto3-lite[s3]>=12.0.0", "types-aioboto3-lite[s3]>=12.0.0",
"boto3-stubs[s3]>=1.33.10",
] ]
[tool.ruff]
extend-select = ["W", "C90", "I", "N", "UP", "S", "BLE", "B", "A", "COM", "C4", "DTZ", "T10", "EM", "ISC", "T20", "PT", "RSE", "RET", "SIM", "PTH", "ERA", "PGH", "PL", "TRY", "RUF"]
extend-ignore = ["S101", "ISC002", "ISC002", "COM812"]

View file

@ -1,7 +1,8 @@
from abc import ABC, abstractmethod
import random import random
from abc import ABC, abstractmethod
from typing import Any
from pydantic import BaseModel, UUID4 from pydantic import UUID4, BaseModel
from secretsanta.settings import Config, get_config from secretsanta.settings import Config, get_config
@ -42,3 +43,7 @@ class IService(ABC):
def __init__(self, repo: IRepo, config: Config | None = None): def __init__(self, repo: IRepo, config: Config | None = None):
self.repo = repo self.repo = repo
self.config = config or get_config() self.config = config or get_config()
@abstractmethod
async def run(self, *args, **kwargs) -> Any:
... # pragma: no cover

View file

@ -5,12 +5,12 @@ import aioboto3
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from secretsanta.domain import Group, IRepo from secretsanta.domain import Group, IRepo
from secretsanta.settings import get_config from secretsanta.settings import Config, get_config
class S3Repo(IRepo): class S3Repo(IRepo):
def __init__(self): def __init__(self, config: Config | None = None):
self.config = get_config() self.config = config or get_config()
self.session = aioboto3.Session( self.session = aioboto3.Session(
aws_access_key_id=self.config.s3_access_key_id, aws_access_key_id=self.config.s3_access_key_id,
aws_secret_access_key=self.config.s3_secret_access_key, aws_secret_access_key=self.config.s3_secret_access_key,
@ -34,7 +34,7 @@ class S3Repo(IRepo):
Key=f"secretsanta/{group_uuid}.json", Key=f"secretsanta/{group_uuid}.json",
) )
except ClientError: except ClientError:
return return None
else: else:
group_raw = await s3_obj["Body"].read() group_raw = await s3_obj["Body"].read()
return Group(**json.loads(group_raw)) return Group(**json.loads(group_raw))

View file

@ -32,12 +32,13 @@ class CreateGroupService(IService):
class GetGroupParticipantsService(IService): class GetGroupParticipantsService(IService):
async def run(self, group_uuid: str) -> list[str] | None: async def run(self, group_uuid: str) -> list[str] | None:
group = await self.repo.get(group_uuid) group = await self.repo.get(group_uuid)
if group: if not group:
return participants2url( return None
participants=group.participants, return participants2url(
group_uuid=group.uuid, participants=group.participants,
server_url=self.config.server_url, group_uuid=group.uuid,
) server_url=self.config.server_url,
)
class GetPairService(IService): class GetPairService(IService):

View file

@ -55,7 +55,7 @@ def repo_with_data(repo, group):
@pytest.fixture() @pytest.fixture()
def config(): def config():
return get_config( return get_config(
s3_bucket_name="foo", s3_secret_access_key="foo", s3_access_key_id="foo" s3_bucket_name="foo", s3_secret_access_key="testing", s3_access_key_id="testing", s3_region="us-east-1"
) )

View file

@ -46,6 +46,10 @@ async def test_get_participants(get_participants_service, group, config):
) )
async def test_get_participants_returns_none(get_participants_service, group):
assert not await get_participants_service.run(group.uuid)
@pytest.mark.usefixtures("repo_with_data") @pytest.mark.usefixtures("repo_with_data")
async def test_get_pair(get_pair_service, group): async def test_get_pair(get_pair_service, group):
assert group.pairs assert group.pairs