From 7db1671fadb07f49ba46abda4ca9c1368c01dbc4 Mon Sep 17 00:00:00 2001 From: Andrey Kataev Date: Tue, 14 Oct 2025 16:24:11 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=9F=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=20getMods=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20vault?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 8 - .just/common.just | 56 ------- Dockerfile | 22 ++- configs/dev.yaml | 13 ++ configs/prod.yaml | 13 ++ justfile | 27 ++-- .../20251009093017_new_mods_table.sql | 0 pdm.lock | 153 +----------------- pyproject.toml | 1 - src/modservice/handler/create_mod.py | 1 - src/modservice/handler/get_mods.py | 11 +- src/modservice/repository/get_mods.py | 1 - src/modservice/server.py | 2 +- src/modservice/settings.py | 114 ++----------- tools/common.just | 44 +++++ tools/load_envs.sh | 73 +++++++++ 16 files changed, 192 insertions(+), 347 deletions(-) delete mode 100644 .env.example delete mode 100644 .just/common.just create mode 100644 configs/dev.yaml create mode 100644 configs/prod.yaml rename {src/migrations => migrations}/20251009093017_new_mods_table.sql (100%) create mode 100644 tools/common.just create mode 100644 tools/load_envs.sh diff --git a/.env.example b/.env.example deleted file mode 100644 index 831d8d4..0000000 --- a/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -HOST=0.0.0.0 -PORT=7777 - -INFISICAL_SERVER_TOKEN=SERVER_TOKEN -INFISICAL_PROJECT_ID=PROJECT_ID -INFISICAL_HOST=https://app.infisical.com -INFISICAL_ENVIRONMENT=dev -INFISICAL_SECRET_PATH=/ \ No newline at end of file diff --git a/.just/common.just b/.just/common.just deleted file mode 100644 index ce6cbae..0000000 --- a/.just/common.just +++ /dev/null @@ -1,56 +0,0 @@ -MKDIR := if os() == 'windows' { 'powershell -Command "New-Item -ItemType Directory -Force -Path"' } else { 'mkdir -p' } -RM := if os() == 'windows' { 'powershell -NoProfile -Command "Remove-Item -Path ' + TMP_DIR + ' -Recurse -Force"' } else { 'rm -rf ' + TMP_DIR } -DOWN := if os() == 'windows' { 'powershell -Command "Invoke-WebRequest -Uri"' } else { 'wget' } -DOWN_OUT := if os() == 'windows' { '-OutFile' } else { '-O' } -PY_FIX_IMPORTS := if os() == 'windows' { - "Get-ChildItem -Path '" + OUT_DIR + "' -Filter '*_pb2_grpc.py' | ForEach-Object { (Get-Content $_.FullName) -replace '^\\s*import (.*_pb2)', 'from . import $1' | Set-Content -Path $_.FullName -Encoding UTF8 }" -} else { - 'bash -lc "for f in ' + OUT_DIR + '/*_pb2_grpc.py; do [ -f \"$f\" ] && sed -i \"s/^import \\(.*_pb2\\)/from . import \\1/\" \"$f\"; done"' -} - -clean: - {{RM}} - -fetch-proto: - {{MKDIR}} "{{TMP_DIR}}" - {{DOWN}} "https://raw.githubusercontent.com/esclient/protos/{{PROTO_TAG}}/{{PROTO_NAME}}" {{DOWN_OUT}} "{{TMP_DIR}}/{{PROTO_NAME}}" - -docker-build: - docker build --build-arg PORT={{ env_var('PORT') }} -t {{SERVICE_NAME}} . - -run: docker-build - docker run --rm -it \ - --env-file .env \ - -p {{ env_var('PORT') }}:{{ env_var('PORT') }} \ - -v {{invocation_directory()}}:/app \ - -e WATCHFILES_FORCE_POLLING=true \ - {{SERVICE_NAME}} - -gen-stubs: fetch-proto - {{MKDIR}} "{{OUT_DIR}}" - pdm run python -m grpc_tools.protoc \ - --proto_path="{{TMP_DIR}}" \ - --python_out="{{OUT_DIR}}" \ - --grpc_python_out="{{OUT_DIR}}" \ - --pyi_out="{{OUT_DIR}}" \ - "{{TMP_DIR}}/{{PROTO_NAME}}" - {{PY_FIX_IMPORTS}} - -update: gen-stubs clean - -format: - black . - isort . - ruff check . --fix - -lint: - black --check . - isort . --check --diff - flake8 . - ruff check . - mypy --strict . - -test: - pytest - -prepare: format lint test diff --git a/Dockerfile b/Dockerfile index c2fca5a..44acd7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,25 @@ FROM python:3.13-slim -RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y wget curl jq && \ + wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && \ + chmod +x /usr/local/bin/yq && \ + apt-get remove -y wget && apt-get autoremove -y && \ + apt-get clean && rm -rf /var/lib/apt/lists/* WORKDIR /app -RUN pip install --no-cache-dir pdm - COPY pyproject.toml pdm.lock ./ -RUN pdm export --group dev --without-hashes -f requirements > /tmp/req.text \ - && pip install --no-cache-dir -r /tmp/req.text \ - && rm /tmp/req.text +RUN pip install --no-cache-dir pdm && \ + pdm export --without-hashes -f requirements > /tmp/req.txt && \ + pip install --no-cache-dir -r /tmp/req.txt && \ + pip uninstall -y pdm && \ + rm /tmp/req.txt COPY . . RUN pip install --no-cache-dir -e . +RUN chmod +x tools/load_envs.sh + +ENV ENV=prod +ENV PYTHONPATH=src -CMD ["watchfiles", "run-server"] \ No newline at end of file +CMD ["./tools/load_envs.sh", "watchfiles", "run-server"] diff --git a/configs/dev.yaml b/configs/dev.yaml new file mode 100644 index 0000000..9ee32ad --- /dev/null +++ b/configs/dev.yaml @@ -0,0 +1,13 @@ +DATABASE_URL: "postgresql://esclient_devmods:XKP$GP2RAJXDVFeR@pg4.sweb.ru:5433/esclient_devmods" +HOST: "0.0.0.0" +PORT: "7004" + +LOG_LEVEL: "INFO" +LOG_FORMAT: "%(asctime)s %(levelname)-8s [%(name)s] %(message)s" +LOG_DATEFMT: "%Y-%m-%d %H:%M:%S" + +S3_API_ENDPOINT: "https://s3.esclient.ru" +S3_ACCESS_KEY: "dev-access" +S3_SECRET_KEY: "5ece9238b18dc29d5e0724b3ae577cdf90b8f78b5eaf62ca" +S3_BUCKET_NAME: "dev" +S3_VERIFY: "true" diff --git a/configs/prod.yaml b/configs/prod.yaml new file mode 100644 index 0000000..7e3f560 --- /dev/null +++ b/configs/prod.yaml @@ -0,0 +1,13 @@ +DATABASE_URL: "{vault:mod-service/prod#database_url}" +HOST: "{vault:mod-service/prod#host}" +PORT: "{vault:mod-service/prod#port}" + +LOG_LEVEL: "WARN" +LOG_FORMAT: "%(asctime)s %(levelname)-8s [%(name)s] %(message)s" +LOG_DATEFMT: "%Y-%m-%d %H:%M:%S" + +S3_API_ENDPOINT: "https://s3.esclient.ru" +S3_ACCESS_KEY: "{vault:mod-service/prod#s3_access_key}" +S3_SECRET_KEY: "{vault:mod-service/prod#s3_secret_key}" +S3_BUCKET_NAME: "prod" +S3_VERIFY: "true" diff --git a/justfile b/justfile index 01122db..45dc9a2 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,8 @@ -set windows-shell := ["powershell", "-NoProfile", "-Command"] +set windows-shell := ["sh", "-c"] set dotenv-load := true -COMMON_URL := 'https://raw.githubusercontent.com/esclient/tools/refs/heads/main/python/common.just' +COMMON_JUST_URL := 'https://raw.githubusercontent.com/esclient/tools/refs/heads/main/python/common.just' +LOAD_ENVS_URL := 'https://raw.githubusercontent.com/esclient/tools/refs/heads/main/load_envs.sh' PROTO_TAG := 'v0.1.2' PROTO_NAME := 'mod.proto' @@ -9,23 +10,17 @@ TMP_DIR := '.proto' OUT_DIR := 'src/modservice/grpc' SERVICE_NAME := 'mod' -MKDIR_DOTJUST := if os() == 'windows' { - 'New-Item -ItemType Directory -Force -Path ".just" | Out-Null' -} else { - 'mkdir -p .just' -} +MKDIR_TOOLS := 'mkdir -p tools' -FETCH_CMD := if os() == 'windows' { - 'Invoke-WebRequest -Uri ' + COMMON_URL + ' -OutFile .just/common.just' -} else { - 'curl -fsSL ' + COMMON_URL + ' -o .just/common.just' -} +FETCH_COMMON_JUST := 'curl -fsSL ' + COMMON_JUST_URL + ' -o tools/common.just' +FETCH_LOAD_ENVS := 'curl -fsSL ' + LOAD_ENVS_URL + ' -o tools/load_envs.sh' -import? '.just/common.just' +import? 'tools/common.just' default: @just --list -fetch-common: - {{ MKDIR_DOTJUST }} - {{ FETCH_CMD }} +fetch-tools: + {{ MKDIR_TOOLS }} + {{ FETCH_COMMON_JUST }} + {{ FETCH_LOAD_ENVS }} diff --git a/src/migrations/20251009093017_new_mods_table.sql b/migrations/20251009093017_new_mods_table.sql similarity index 100% rename from src/migrations/20251009093017_new_mods_table.sql rename to migrations/20251009093017_new_mods_table.sql diff --git a/pdm.lock b/pdm.lock index ab6937f..8a4448b 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,20 +5,11 @@ groups = ["default", "dev"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:558f597ae02073d90ae26ca0c77732b3339361c0680d038c2307fcebd9305251" +content_hash = "sha256:0968b1df863901d680c9d4e358cba016a42a81d152f3444e16cd5ab798bbe1ca" [[metadata.targets]] requires_python = "~=3.13" -[[package]] -name = "aenum" -version = "3.1.16" -summary = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" -groups = ["default"] -files = [ - {file = "aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf"}, -] - [[package]] name = "annotated-types" version = "0.7.0" @@ -75,22 +66,6 @@ files = [ {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, ] -[[package]] -name = "boto3" -version = "1.40.26" -requires_python = ">=3.9" -summary = "The AWS SDK for Python" -groups = ["default"] -dependencies = [ - "botocore<1.41.0,>=1.40.26", - "jmespath<2.0.0,>=0.7.1", - "s3transfer<0.14.0,>=0.13.0", -] -files = [ - {file = "boto3-1.40.26-py3-none-any.whl", hash = "sha256:8272deb4b82c4a0faa1231c2cd5c6d267d71ed6265abef545c1d5b7f0aa936d8"}, - {file = "boto3-1.40.26.tar.gz", hash = "sha256:9a71684825cfd4548027f254eadf4dafb7fccc7523f20e2a1cb74033f4d74a6b"}, -] - [[package]] name = "botocore" version = "1.40.26" @@ -108,83 +83,6 @@ files = [ {file = "botocore-1.40.26.tar.gz", hash = "sha256:f8f46b3978b7c324f4c0bef03505870c4c5240c736bfb63318da091942a29710"}, ] -[[package]] -name = "certifi" -version = "2025.8.3" -requires_python = ">=3.7" -summary = "Python package for providing Mozilla's CA Bundle." -groups = ["default"] -files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.3" -requires_python = ">=3.7" -summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -groups = ["default"] -files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, -] - [[package]] name = "click" version = "8.2.1" @@ -350,23 +248,6 @@ files = [ {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] -[[package]] -name = "infisicalsdk" -version = "1.0.11" -summary = "Official Infisical SDK for Python (Latest)" -groups = ["default"] -dependencies = [ - "aenum", - "boto3~=1.35", - "botocore~=1.35", - "python-dateutil", - "requests~=2.32", -] -files = [ - {file = "infisicalsdk-1.0.11-py3-none-any.whl", hash = "sha256:d86533a9d1c63baf65c5f52b14c2341dd4b06267c3d5dcad20d379498decf547"}, - {file = "infisicalsdk-1.0.11.tar.gz", hash = "sha256:01fe9a6f6a7ef2219f8ebf9ecb7897f80cc61f1044cef8017b955049293e79b0"}, -] - [[package]] name = "iniconfig" version = "2.1.0" @@ -793,23 +674,6 @@ files = [ {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, ] -[[package]] -name = "requests" -version = "2.32.5" -requires_python = ">=3.9" -summary = "Python HTTP for Humans." -groups = ["default"] -dependencies = [ - "certifi>=2017.4.17", - "charset-normalizer<4,>=2", - "idna<4,>=2.5", - "urllib3<3,>=1.21.1", -] -files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, -] - [[package]] name = "ruff" version = "0.13.2" @@ -838,20 +702,6 @@ files = [ {file = "ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff"}, ] -[[package]] -name = "s3transfer" -version = "0.13.1" -requires_python = ">=3.9" -summary = "An Amazon S3 Transfer Manager" -groups = ["default"] -dependencies = [ - "botocore<2.0a.0,>=1.37.4", -] -files = [ - {file = "s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724"}, - {file = "s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf"}, -] - [[package]] name = "setuptools" version = "80.9.0" @@ -938,6 +788,7 @@ version = "2.5.0" requires_python = ">=3.9" summary = "HTTP library with thread-safe connection pooling, file post, and more." groups = ["default"] +marker = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, diff --git a/pyproject.toml b/pyproject.toml index a05b625..987e58b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,6 @@ dependencies = [ "psycopg2-binary==2.9.10", "watchfiles==1.1.0", "botocore>=1.34.0", - "infisicalsdk>=1.0.11", "python-dotenv>=1.0.0", ] requires-python = ">=3.13" diff --git a/src/modservice/handler/create_mod.py b/src/modservice/handler/create_mod.py index 323353d..9dec4a1 100644 --- a/src/modservice/handler/create_mod.py +++ b/src/modservice/handler/create_mod.py @@ -9,7 +9,6 @@ def CreateMod( request: mod_pb2.CreateModRequest, context: grpc.ServicerContext, # noqa: ARG001 ) -> mod_pb2.CreateModResponse: - # Создаем мод mod_id, s3_key, upload_url = service.create_mod( request.title, request.author_id, diff --git a/src/modservice/handler/get_mods.py b/src/modservice/handler/get_mods.py index 052ddaa..9634593 100644 --- a/src/modservice/handler/get_mods.py +++ b/src/modservice/handler/get_mods.py @@ -1,8 +1,15 @@ import grpc +from modservice.constants import STATUS_BANNED, STATUS_HIDDEN, STATUS_UPLOADED from modservice.grpc import mod_pb2 from modservice.service.service import ModService +STATUS_TO_PROTO: dict[str, mod_pb2.ModStatus] = { + STATUS_UPLOADED: mod_pb2.ModStatus.MOD_STATUS_UPLOADED, + STATUS_BANNED: mod_pb2.ModStatus.MOD_STATUS_BANNED, + STATUS_HIDDEN: mod_pb2.ModStatus.MOD_STATUS_HIDDEN, +} + def GetMods( service: ModService, @@ -19,14 +26,12 @@ def GetMods( title=mod_data["title"], description=mod_data["description"], version=mod_data["version"], - status=mod_data["status"], + status=STATUS_TO_PROTO[mod_data["status"]], created_at=( mod_data["created_at"] if mod_data.get("created_at") else None ), - # TODO: Раскомментировать когда добавятся в БД и proto: # avatar_url=mod_data.get("avatar_url", ""), # download_count=mod_data.get("download_count", 0), - # rating=mod_data.get("rating", 0.0), # tags=mod_data.get("tags", []), # updated_at=mod_data["updated_at"] if mod_data.get("updated_at") else None, ) diff --git a/src/modservice/repository/get_mods.py b/src/modservice/repository/get_mods.py index 96722c6..ef99c72 100644 --- a/src/modservice/repository/get_mods.py +++ b/src/modservice/repository/get_mods.py @@ -12,7 +12,6 @@ def get_mods( # NOTE: Поля для будущего добавления в БД: # - avatar_url # - download_count - # - rating # - tags # - updated_at cursor.execute( diff --git a/src/modservice/server.py b/src/modservice/server.py index 9c1ab61..562e035 100644 --- a/src/modservice/server.py +++ b/src/modservice/server.py @@ -15,7 +15,7 @@ def serve() -> None: - settings = Settings.load() + settings = Settings() settings.configure_logging() logger = logging.getLogger(__name__) diff --git a/src/modservice/settings.py b/src/modservice/settings.py index 973e1b1..7ad99db 100644 --- a/src/modservice/settings.py +++ b/src/modservice/settings.py @@ -1,117 +1,27 @@ import logging -import os -from typing import Any -from dotenv import load_dotenv -from infisical_sdk import InfisicalSDKClient # type: ignore[import-untyped] +from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict -ENV_FILE = ".env" - -REQUIRED_ENV_VARS = [ - "HOST", - "PORT", - "INFISICAL_SERVER_TOKEN", - "INFISICAL_PROJECT_ID", - "INFISICAL_HOST", - "INFISICAL_ENVIRONMENT", - "INFISICAL_SECRET_PATH", -] - -SECRETS = [ - "DATABASE_URL", - "LOG_DATEFMT", - "LOG_FORMAT", - "LOG_LEVEL", - "S3_ACCESS_KEY", - "S3_API_ENDPOINT", - "S3_BUCKET_NAME", - "S3_SECRET_KEY", - "S3_SSL_VERIFY", -] - class Settings(BaseSettings): model_config = SettingsConfigDict( - env_file=ENV_FILE, - env_file_encoding="utf-8", extra="ignore", ) - host: str - port: int - database_url: str - log_level: str - log_format: str - log_datefmt: str - s3_api_endpoint: str - s3_access_key: str - s3_secret_key: str - s3_bucket_name: str - s3_verify: bool - - @classmethod - def load(cls) -> "Settings": - load_dotenv(ENV_FILE) - missing_env = [ - name for name in REQUIRED_ENV_VARS if not os.environ.get(name) - ] - if missing_env: - raise ValueError( - f"Missing required environment variables: {', '.join(missing_env)}" - ) - - secrets = cls._load_secrets( - host=os.environ["INFISICAL_HOST"], - server_token=os.environ["INFISICAL_SERVER_TOKEN"], - project_id=os.environ["INFISICAL_PROJECT_ID"], - environment_slug=os.environ["INFISICAL_ENVIRONMENT"], - secret_path=os.environ["INFISICAL_SECRET_PATH"], - secret_names=SECRETS, - ) - - return cls( - host=os.environ["HOST"], - port=os.environ["PORT"], - database_url=secrets["DATABASE_URL"], - s3_api_endpoint=secrets["S3_API_ENDPOINT"], - s3_access_key=secrets["S3_ACCESS_KEY"], - s3_secret_key=secrets["S3_SECRET_KEY"], - s3_bucket_name=secrets["S3_BUCKET_NAME"], - log_level=secrets["LOG_LEVEL"], - log_format=secrets["LOG_FORMAT"], - log_datefmt=secrets["LOG_DATEFMT"], - s3_verify=secrets["S3_SSL_VERIFY"], - ) - - @staticmethod - def _load_secrets( - host: str, - server_token: str, - project_id: str, - environment_slug: str, - secret_path: str, - secret_names: list[str], - ) -> dict[str, str]: - client: Any = InfisicalSDKClient(host=host, token=server_token) - - results: dict[str, str] = {} - for name in secret_names: - try: - secret = client.secrets.get_secret_by_name( - secret_name=name, - project_id=project_id, - environment_slug=environment_slug, - secret_path=secret_path, - ) + host: str = Field(validation_alias="HOST") + port: int = Field(validation_alias="PORT") + database_url: str = Field(validation_alias="DATABASE_URL") - results[name] = secret.secretValue - except Exception as e: - raise ValueError( - f"Infisical: failed to fetch secret '{name}': {e}" - ) from e + log_level: str = Field(validation_alias="LOG_LEVEL") + log_format: str = Field(validation_alias="LOG_FORMAT") + log_datefmt: str = Field(validation_alias="LOG_DATEFMT") - return results + s3_api_endpoint: str = Field(validation_alias="S3_API_ENDPOINT") + s3_access_key: str = Field(validation_alias="S3_ACCESS_KEY") + s3_secret_key: str = Field(validation_alias="S3_SECRET_KEY") + s3_bucket_name: str = Field(validation_alias="S3_BUCKET_NAME") + s3_verify: bool = Field(validation_alias="S3_VERIFY") def configure_logging(self) -> None: logging.basicConfig( diff --git a/tools/common.just b/tools/common.just new file mode 100644 index 0000000..883b36c --- /dev/null +++ b/tools/common.just @@ -0,0 +1,44 @@ +MKDIR := 'mkdir -p' +RM := 'rm -rf ' + TMP_DIR +DOWN := 'curl -fsSL' +DOWN_OUT := '-o' +PY_FIX_IMPORTS := 'for f in ' + OUT_DIR + '/*_pb2_grpc.py; do [ -f "$f" ] && sed -i "s/^import \\(.*_pb2\\)/from . import \\1/" "$f"; done' + +clean: + {{RM}} + +fetch-proto: + {{MKDIR}} "{{TMP_DIR}}" + {{DOWN}} "https://raw.githubusercontent.com/esclient/protos/{{PROTO_TAG}}/{{PROTO_NAME}}" {{DOWN_OUT}} "{{TMP_DIR}}/{{PROTO_NAME}}" + +run: + ENV=dev PYTHONPATH=src ./tools/load_envs.sh pdm run run-server + +gen-stubs: fetch-proto + {{MKDIR}} "{{OUT_DIR}}" + pdm run python -m grpc_tools.protoc \ + --proto_path="{{TMP_DIR}}" \ + --python_out="{{OUT_DIR}}" \ + --grpc_python_out="{{OUT_DIR}}" \ + --pyi_out="{{OUT_DIR}}" \ + "{{TMP_DIR}}/{{PROTO_NAME}}" + {{PY_FIX_IMPORTS}} + +update: gen-stubs clean + +format: + black . + isort . + ruff check . --fix + +lint: + black --check . + isort . --check --diff + flake8 . + ruff check . + mypy --strict . + +test: + pytest + +prepare: format lint test diff --git a/tools/load_envs.sh b/tools/load_envs.sh new file mode 100644 index 0000000..cbc4a4b --- /dev/null +++ b/tools/load_envs.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +if [ "$ENV" = "prod" ] && [ -f .env ]; then + export $(grep -v '^#' .env | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed 's/"//g' | sed "s/'//g" | xargs) +fi + +CONFIG="configs/${ENV}.yaml" +[ ! -f "$CONFIG" ] && echo "ERROR: Config not found: $CONFIG" >&2 && exit 1 + +eval "$(yq eval 'to_entries | .[] | "export " + .key + "='\''" + (.value | tostring) + "'\''"' "$CONFIG")" + +if [ "$ENV" = "dev" ]; then + exec "$@" +fi + +VAULT_REFS_COUNT=$(env | grep -c '{vault:' || true) + +if [ "$VAULT_REFS_COUNT" -eq 0 ]; then + exec "$@" +fi + +if [ -z "$VAULT_ADDR" ] || [ -z "$VAULT_TOKEN" ]; then + echo "ERROR: Config contains vault references but VAULT_ADDR or VAULT_TOKEN is not set" >&2 + exit 1 +fi + +for var in $(env | grep '{vault:' | cut -d= -f1); do + value="${!var}" + + while [[ "$value" =~ \{vault:([^}]+)\} ]]; do + ref="${BASH_REMATCH[1]}" + path="${ref%#*}" + key="${ref##*#}" + + vault_mount="${VAULT_MOUNT_POINT:-secret}" + vault_url="${VAULT_ADDR}/v1/${vault_mount}/data/${path}" + + set +e + vault_response=$(curl -s -S -w "\n%{http_code}" \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + "$vault_url" 2>&1) + curl_exit_code=$? + set -e + + if [ $curl_exit_code -ne 0 ]; then + echo "ERROR: Failed to fetch secret from Vault: $vault_url" >&2 + echo "Curl response: $vault_response" >&2 + exit 1 + fi + + http_code=$(echo "$vault_response" | tail -n1) + vault_response=$(echo "$vault_response" | sed '$d') + + if [ "$http_code" != "200" ]; then + echo "ERROR: Vault API returned HTTP $http_code for $vault_url" >&2 + exit 1 + fi + + secret=$(echo "$vault_response" | jq -r ".data.data.$key // .data.$key") + + if [ "$secret" = "null" ] || [ -z "$secret" ]; then + echo "ERROR: Secret key '$key' not found at path '$path'" >&2 + exit 1 + fi + + value="${value/\{vault:$ref\}/$secret}" + done + + export "$var=$value" +done + +exec "$@" From 721927b350647b038bb7ce5c1dabc96517c7f012 Mon Sep 17 00:00:00 2001 From: Andrey Kataev Date: Tue, 14 Oct 2025 16:34:55 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BD=D0=B0=D1=80=D0=B0=20=D0=B2=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D1=84=D0=BB=D0=BE=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint-and-test.yml | 5 +++-- sonar-project.properties | 10 ---------- 2 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 sonar-project.properties diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index f660173..d697eaa 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -13,7 +13,8 @@ permissions: jobs: lint-and-test: - uses: esclient/tools/.github/workflows/lint-and-test-python.yml@main + uses: esclient/tools/.github/workflows/lint-and-test-python.yml@v1.0.0 with: python-version: '3.13.7' - secrets: inherit + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 522c474..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,10 +0,0 @@ -# SonarQube Configuration - -# Exclude auto-generated gRPC files from analysis -sonar.exclusions=**/grpc/**/*_pb2.py,**/grpc/**/*_pb2.pyi,**/grpc/**/*_pb2_grpc.py - -# Exclude common directories -sonar.coverage.exclusions=**/grpc/**,**/tests/**,**/__pycache__/** - -# Python specific settings -sonar.python.version=3.13 From 828a5387286f59e4b5766bd4b912c0a9a7fdb8cf Mon Sep 17 00:00:00 2001 From: Andrey Kataev Date: Tue, 14 Oct 2025 16:36:31 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8E=20=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BA=D1=84=D0=BB=D0=BE=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index d697eaa..8e921ad 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -13,7 +13,7 @@ permissions: jobs: lint-and-test: - uses: esclient/tools/.github/workflows/lint-and-test-python.yml@v1.0.0 + uses: esclient/tools/.github/workflows/lint-and-test-python.yml@v1.0.4 with: python-version: '3.13.7' secrets: