From d974c138a01f4ad23b54e390bc5102be16147de5 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 10:18:04 +0100 Subject: [PATCH 1/9] Make deployment read only ready --- Dockerfile | 13 ++++++------- task_registry/core/config.py | 1 + task_registry/core/log.py | 28 ++++++++++++++++++---------- task_registry/lifespan.py | 2 +- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 719bf0a..610c2bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,19 +47,18 @@ RUN pyright FROM project-base AS production - RUN groupadd tr && \ adduser --uid 100 --system --ingroup tr tr -RUN chown tr:tr /app/ +RUN chown -R tr:tr /app/ USER tr -COPY --chown=root:root --chmod=755 task_registry /app/task_registry -COPY --chown=root:root --chmod=755 instruments /app/instruments -COPY --chown=root:root --chmod=755 requirements /app/requirements -COPY --chown=root:root --chmod=755 measures /app/measures -COPY --chown=root:root --chmod=755 LICENSE /app/LICENSE +COPY --chown=tr:tr --chmod=755 task_registry /app/task_registry +COPY --chown=tr:tr --chmod=755 instruments /app/instruments +COPY --chown=tr:tr --chmod=755 requirements /app/requirements +COPY --chown=tr:tr --chmod=755 measures /app/measures +COPY --chown=tr:tr --chmod=755 LICENSE /app/LICENSE ENV PYTHONPATH=/app/ WORKDIR /app/ diff --git a/task_registry/core/config.py b/task_registry/core/config.py index c4290f8..4cef979 100644 --- a/task_registry/core/config.py +++ b/task_registry/core/config.py @@ -25,6 +25,7 @@ class Settings(BaseSettings): LOGGING_LEVEL: LoggingLevelType = "INFO" LOGGING_CONFIG: dict[str, Any] | None = None + LOG_TO_FILE: bool = False def get_settings() -> Settings: diff --git a/task_registry/core/log.py b/task_registry/core/log.py index 7a14f32..601adea 100644 --- a/task_registry/core/log.py +++ b/task_registry/core/log.py @@ -25,28 +25,36 @@ "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, - "file": { - "formatter": "generic", - "()": "logging.handlers.RotatingFileHandler", - "filename": "task-registry.log", - "maxBytes": LOGGING_SIZE, - "backupCount": LOGGING_BACKUP_COUNT, - }, }, "loggers": { - "": {"handlers": ["console", "file"], "level": "DEBUG", "propagate": False}, + "": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, "httpcore": { - "handlers": ["console", "file"], + "handlers": ["console"], "level": "ERROR", "propagate": False, }, }, } +FILE_HANDLER_CONFIG = { + "formatter": "generic", + "()": "logging.handlers.RotatingFileHandler", + "filename": "task-registry.log", + "maxBytes": LOGGING_SIZE, + "backupCount": LOGGING_BACKUP_COUNT, +} -def configure_logging(level: LoggingLevelType = "INFO", config: dict[str, Any] | None = None) -> None: + +def configure_logging( + level: LoggingLevelType = "INFO", config: dict[str, Any] | None = None, log_to_file: bool = False +) -> None: log_config = copy.deepcopy(LOGGING_CONFIG) + if log_to_file: + log_config["handlers"]["file"] = FILE_HANDLER_CONFIG.copy() + for logger_config in log_config["loggers"].values(): + logger_config["handlers"].append("file") + if config: log_config.update(config) diff --git a/task_registry/lifespan.py b/task_registry/lifespan.py index f5b77d9..638bd9f 100644 --- a/task_registry/lifespan.py +++ b/task_registry/lifespan.py @@ -9,7 +9,7 @@ CACHED_REGISTRY = CachedRegistry() -configure_logging(get_settings().LOGGING_LEVEL, get_settings().LOGGING_CONFIG) +configure_logging(get_settings().LOGGING_LEVEL, get_settings().LOGGING_CONFIG, get_settings().LOG_TO_FILE) logger = logging.getLogger(__name__) From 732a88d4b4e0508d938bd37be8ca12b8410f44c0 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 10:25:25 +0100 Subject: [PATCH 2/9] Fix the github greeting action --- .github/workflows/first-interaction.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index 9dc6bae..8cf2573 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -11,12 +11,12 @@ jobs: steps: - uses: actions/first-interaction@v3 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: | + repo_token: ${{ secrets.GITHUB_TOKEN }} + issue_message: | Welcome to our community :hugs: and thank you for your first contribution. As a first time contributor please make sure to review our [contribution guidelines](../blob/main/CONTRIBUTING.md) :heart: - pr-message: | + pr_message: | Welcome to our community :hugs: and thank you for your first contribution. As a first time contributor please make sure to review our [contribution guidelines](../blob/main/CONTRIBUTING.md) :heart: From 339de53fd7e655edf8f91d28afb11d7cb74bf238 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 10:36:50 +0100 Subject: [PATCH 3/9] Updated security issues for dependencies --- Dockerfile | 4 +- poetry.lock | 135 +++++++++++++++++++++++++++++++++++++------------ pyproject.toml | 4 +- 3 files changed, 107 insertions(+), 36 deletions(-) diff --git a/Dockerfile b/Dockerfile index 610c2bf..d9caa43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,8 +47,8 @@ RUN pyright FROM project-base AS production -RUN groupadd tr && \ - adduser --uid 100 --system --ingroup tr tr +RUN groupadd -g 1000 tr && \ + adduser --uid 1000 --system --ingroup tr tr RUN chown -R tr:tr /app/ diff --git a/poetry.lock b/poetry.lock index 1e79acc..4cb18f6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,16 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. + +[[package]] +name = "annotated-doc" +version = "0.0.4" +description = "Document parameters, class attributes, return types, and variables inline, with Annotated." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, + {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, +] [[package]] name = "annotated-types" @@ -6,6 +18,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +30,7 @@ version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main", "test"] files = [ {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, @@ -38,6 +52,7 @@ version = "24.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, @@ -57,6 +72,7 @@ version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["test"] files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -68,6 +84,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -79,6 +96,7 @@ version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["test"] files = [ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, @@ -193,6 +211,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -207,10 +226,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", test = "sys_platform == \"win32\""} [[package]] name = "coverage" @@ -218,6 +239,7 @@ version = "7.6.9" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, @@ -292,6 +314,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -299,23 +322,26 @@ files = [ [[package]] name = "fastapi" -version = "0.115.6" +version = "0.125.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, - {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, + {file = "fastapi-0.125.0-py3-none-any.whl", hash = "sha256:2570ec4f3aecf5cca8f0428aed2398b774fcdfee6c2116f86e80513f2f86a7a1"}, + {file = "fastapi-0.125.0.tar.gz", hash = "sha256:16b532691a33e2c5dee1dac32feb31dc6eb41a3dd4ff29a95f9487cb21c054c0"}, ] [package.dependencies] +annotated-doc = ">=0.0.2" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.42.0" +starlette = ">=0.40.0,<0.51.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" @@ -323,6 +349,7 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -339,6 +366,7 @@ version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -421,29 +449,31 @@ test = ["objgraph", "psutil"] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] certifi = "*" -h11 = ">=0.13,<0.15" +h11 = ">=0.16" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] @@ -457,6 +487,7 @@ version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -482,6 +513,7 @@ version = "2.6.3" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, @@ -496,6 +528,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -510,6 +543,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -521,6 +555,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -542,6 +577,7 @@ version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, @@ -556,6 +592,7 @@ version = "0.9.2" description = "Check python packages from requirement.txt and report issues" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "liccheck-0.9.2-py2.py3-none-any.whl", hash = "sha256:15cbedd042515945fe9d58b62e0a5af2f2a7795def216f163bb35b3016a16637"}, {file = "liccheck-0.9.2.tar.gz", hash = "sha256:bdc2190f8e95af3c8f9c19edb784ba7d41ecb2bf9189422eae6112bf84c08cd5"}, @@ -571,6 +608,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -582,6 +620,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -593,6 +632,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -609,6 +649,7 @@ version = "1.49.1" description = "A high-level API to automate web browsers" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8"}, {file = "playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be"}, @@ -629,6 +670,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -644,6 +686,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -662,6 +705,7 @@ version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, @@ -682,6 +726,7 @@ version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, @@ -794,6 +839,7 @@ version = "2.7.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"}, {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"}, @@ -814,6 +860,7 @@ version = "12.0.0" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"}, {file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"}, @@ -831,6 +878,7 @@ version = "1.1.390" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pyright-1.1.390-py3-none-any.whl", hash = "sha256:ecebfba5b6b50af7c1a44c2ba144ba2ab542c227eb49bc1f16984ff714e0e110"}, {file = "pyright-1.1.390.tar.gz", hash = "sha256:aad7f160c49e0fbf8209507a15e17b781f63a86a1facb69ca877c71ef2e9538d"}, @@ -851,6 +899,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -871,6 +920,7 @@ version = "2.1.0" description = "pytest plugin for URL based testing" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"}, {file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"}, @@ -889,6 +939,7 @@ version = "0.30.0" description = "Send responses to httpx." optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pytest-httpx-0.30.0.tar.gz", hash = "sha256:755b8edca87c974dd4f3605c374fda11db84631de3d163b99c0df5807023a19a"}, {file = "pytest_httpx-0.30.0-py3-none-any.whl", hash = "sha256:6d47849691faf11d2532565d0c8e0e02b9f4ee730da31687feae315581d7520c"}, @@ -907,6 +958,7 @@ version = "0.5.2" description = "A pytest wrapper with fixtures for Playwright to automate web browsers" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest_playwright-0.5.2-py3-none-any.whl", hash = "sha256:2c5720591364a1cdf66610b972ff8492512bc380953e043c85f705b78b2ed582"}, {file = "pytest_playwright-0.5.2.tar.gz", hash = "sha256:c6d603df9e6c50b35f057b0528e11d41c0963283e98c257267117f5ed6ba1924"}, @@ -924,6 +976,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -938,6 +991,7 @@ version = "8.0.4" description = "A Python slugify application that also handles Unicode" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, @@ -955,6 +1009,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1017,6 +1072,7 @@ version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, @@ -1032,6 +1088,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1053,6 +1110,7 @@ version = "0.22.3" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, @@ -1165,6 +1223,7 @@ version = "0.6.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, @@ -1192,6 +1251,7 @@ version = "2.10.0" description = "A library implementing the 'SemVer' scheme." optional = false python-versions = ">=2.7" +groups = ["dev"] files = [ {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, @@ -1203,23 +1263,24 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "74.1.3" +version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "setuptools-74.1.3-py3-none-any.whl", hash = "sha256:1cfd66bfcf197bce344da024c8f5b35acc4dcb7ca5202246a75296b4883f6851"}, - {file = "setuptools-74.1.3.tar.gz", hash = "sha256:fbb126f14b0b9ffa54c4574a50ae60673bbe8ae0b1645889d10b3b14f5891d28"}, + {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, + {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "sniffio" @@ -1227,6 +1288,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1234,20 +1296,22 @@ files = [ [[package]] name = "starlette" -version = "0.41.3" +version = "0.50.0" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, - {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, + {file = "starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca"}, + {file = "starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca"}, ] [package.dependencies] -anyio = ">=3.4.0,<5" +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] name = "text-unidecode" @@ -1255,6 +1319,7 @@ version = "1.3" description = "The most basic Text::Unidecode port" optional = false python-versions = "*" +groups = ["test"] files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, @@ -1266,6 +1331,7 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -1277,6 +1343,7 @@ version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, @@ -1288,6 +1355,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "test"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1299,6 +1367,7 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -1316,6 +1385,7 @@ version = "0.30.6" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, @@ -1334,6 +1404,7 @@ version = "20.28.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, @@ -1349,6 +1420,6 @@ docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "s test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "8f9506e571cbef9006043bdb9541f0b7d92f88fe2472d4ac8830cf8294c5c682" +content-hash = "a2929211a03b924216db136d522786ea6659a01a2320d29b7112c52814f5da70" diff --git a/pyproject.toml b/pyproject.toml index 7769cfc..b3de378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,11 +24,11 @@ deploy = "task_registry.data:main" python = "^3.12" jsonschema = "^4.22.0" pyyaml = "^6.0.1" -fastapi = "^0.115.6" +fastapi = "^0.125.0" uvicorn = "^0.30.6" pydantic-settings = "^2.4.0" -setuptools = "^74.1.1" +setuptools = "^78.1.1" click = "^8.1.7" [build-system] requires = ["poetry-core"] From b27fec7d4e5a80d3f6ccceb4e7c9bbf21d39e8f2 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 10:43:59 +0100 Subject: [PATCH 4/9] Add tests for logging to file --- tests/core/test_log.py | 47 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/core/test_log.py b/tests/core/test_log.py index ed9903c..44975cb 100644 --- a/tests/core/test_log.py +++ b/tests/core/test_log.py @@ -1,8 +1,10 @@ import logging +import tempfile +from pathlib import Path import pytest from task_registry.core.config import Settings -from task_registry.core.log import configure_logging +from task_registry.core.log import FILE_HANDLER_CONFIG, configure_logging def test_logging_tr_module(caplog: pytest.LogCaptureFixture): @@ -84,3 +86,46 @@ def test_logging_loglevel(caplog: pytest.LogCaptureFixture, monkeypatch: pytest. logger.critical(message) assert len(caplog.records) == 2 + + +def test_logging_log_to_file(monkeypatch: pytest.MonkeyPatch) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + log_file = Path(tmpdir) / "test.log" + monkeypatch.setitem(FILE_HANDLER_CONFIG, "filename", str(log_file)) + + configure_logging(log_to_file=True) + + # Verify file handler was added to root logger + root_logger = logging.getLogger() + handler_classes = [type(h).__name__ for h in root_logger.handlers] + assert "RotatingFileHandler" in handler_classes + + # Log a message and verify it's written to the file + message = "This is a file log message" + root_logger.info(message) + + # Flush handlers to ensure log is written + for handler in root_logger.handlers: + handler.flush() + + assert log_file.exists() + log_content = log_file.read_text() + assert message in log_content + + +def test_logging_log_to_file_disabled() -> None: + # When log_to_file is False (default), no file handler should be added + configure_logging(log_to_file=False) + + root_logger = logging.getLogger() + handler_classes = [type(h).__name__ for h in root_logger.handlers] + + assert "RotatingFileHandler" not in handler_classes + + +def test_logging_log_to_file_setting(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("LOG_TO_FILE", "false") + + settings = Settings() + + assert settings.LOG_TO_FILE is False From 8d89318550ca46fc1197ccb33d11571f2b6e4e95 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 10:47:08 +0100 Subject: [PATCH 5/9] Fix pyright errors --- task_registry/core/log.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/task_registry/core/log.py b/task_registry/core/log.py index 601adea..7146518 100644 --- a/task_registry/core/log.py +++ b/task_registry/core/log.py @@ -8,7 +8,7 @@ LOGGING_SIZE = 10 * 1024 * 1024 LOGGING_BACKUP_COUNT = 5 -LOGGING_CONFIG = { +LOGGING_CONFIG: dict[str, Any] = { "version": 1, "disable_existing_loggers": False, "formatters": { @@ -36,7 +36,7 @@ }, } -FILE_HANDLER_CONFIG = { +FILE_HANDLER_CONFIG: dict[str, Any] = { "formatter": "generic", "()": "logging.handlers.RotatingFileHandler", "filename": "task-registry.log", @@ -48,7 +48,7 @@ def configure_logging( level: LoggingLevelType = "INFO", config: dict[str, Any] | None = None, log_to_file: bool = False ) -> None: - log_config = copy.deepcopy(LOGGING_CONFIG) + log_config: dict[str, Any] = copy.deepcopy(LOGGING_CONFIG) if log_to_file: log_config["handlers"]["file"] = FILE_HANDLER_CONFIG.copy() From f6309ed988abc7fba9f6646936832d35641d2ca2 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 10:51:30 +0100 Subject: [PATCH 6/9] Fix the license list due to updates of dependencies --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b3de378..a927cdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,6 +123,7 @@ authorized_licenses = [ "Apache Software", "Artistic", "BSD", + "BSD-3-Clause", "GNU General Public License v2 or later (GPLv2+)", "GNU General Public License v3 or later (GPLv3+)", "GNU General Public License (GPL)", @@ -133,3 +134,6 @@ authorized_licenses = [ "Mozilla Public License 2.0 (MPL 2.0)", "Python Software Foundation" ] +# setuptools is MIT licensed but doesn't include license metadata in v78+ +[tool.liccheck.authorized_packages] +setuptools = "78.1.1" From 0290ce51d4eae9e3dcc2af1a231f9d3b7b5ef943 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 11:27:11 +0100 Subject: [PATCH 7/9] Add prometheus endpoint --- Dockerfile | 1 + poetry.lock | 33 +++++++++++++++++++++++++++++++- pyproject.toml | 10 +++++++--- task_registry/api/main.py | 2 +- task_registry/server.py | 18 +++++++++++++++++ tests/api/routes/test_metrics.py | 11 +++++++++++ 6 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 tests/api/routes/test_metrics.py diff --git a/Dockerfile b/Dockerfile index d9caa43..19a082c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ ENV PYTHONUNBUFFERED=1 \ PIP_DISABLE_PIP_VERSION_CHECK=on \ PIP_DEFAULT_TIMEOUT=100 \ POETRY_VIRTUALENVS_IN_PROJECT=true \ + POETRY_NO_INTERACTION=1 \ POETRY_HOME='/usr/local' diff --git a/poetry.lock b/poetry.lock index 4cb18f6..db26a9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -699,6 +699,37 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "prometheus-client" +version = "0.23.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99"}, + {file = "prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prometheus-fastapi-instrumentator" +version = "7.1.0" +description = "Instrument your FastAPI app with Prometheus metrics" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9"}, + {file = "prometheus_fastapi_instrumentator-7.1.0.tar.gz", hash = "sha256:be7cd61eeea4e5912aeccb4261c6631b3f227d8924542d79eaf5af3f439cbe5e"}, +] + +[package.dependencies] +prometheus-client = ">=0.8.0,<1.0.0" +starlette = ">=0.30.0,<1.0.0" + [[package]] name = "pydantic" version = "2.10.3" @@ -1422,4 +1453,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "a2929211a03b924216db136d522786ea6659a01a2320d29b7112c52814f5da70" +content-hash = "7d9908ecc21a206c38e72aa375ad1bddd79baa712fe63552a57bce0f5c925671" diff --git a/pyproject.toml b/pyproject.toml index a927cdf..dab7c4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ pydantic-settings = "^2.4.0" setuptools = "^78.1.1" click = "^8.1.7" +prometheus-fastapi-instrumentator = "^7.1.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" @@ -121,18 +122,21 @@ level = "PARANOID" dependencies = true authorized_licenses = [ "Apache Software", + "Apache-2.0 AND BSD-2-Clause", "Artistic", "BSD", + "BSD-2-Clause", "BSD-3-Clause", "GNU General Public License v2 or later (GPLv2+)", "GNU General Public License v3 or later (GPLv3+)", "GNU General Public License (GPL)", "GNU Library or Lesser General Public License (LGPL)", - "MIT", - "The Unlicense (Unlicense)", + "ISC", "ISC License (ISCL)", + "MIT", "Mozilla Public License 2.0 (MPL 2.0)", - "Python Software Foundation" + "Python Software Foundation", + "The Unlicense (Unlicense)" ] # setuptools is MIT licensed but doesn't include license metadata in v78+ [tool.liccheck.authorized_packages] diff --git a/task_registry/api/main.py b/task_registry/api/main.py index d9a7ae8..0e46d6a 100644 --- a/task_registry/api/main.py +++ b/task_registry/api/main.py @@ -2,7 +2,7 @@ from task_registry.api.routes import health, instruments, measures, requirements, urns api_router = APIRouter() -api_router.include_router(health.router, prefix="/health", tags=["health"]) +api_router.include_router(health.router, prefix="/health", tags=["health"], include_in_schema=False) api_router.include_router(instruments.router, prefix="/instruments", tags=["instruments"]) api_router.include_router(measures.router, prefix="/measures", tags=["measures"]) api_router.include_router(requirements.router, prefix="/requirements", tags=["requirements"]) diff --git a/task_registry/server.py b/task_registry/server.py index f117c8f..9e2509c 100644 --- a/task_registry/server.py +++ b/task_registry/server.py @@ -1,8 +1,16 @@ from fastapi import FastAPI +from prometheus_client import CONTENT_TYPE_LATEST, REGISTRY, generate_latest +from prometheus_client.process_collector import ProcessCollector +from prometheus_fastapi_instrumentator import Instrumentator +from starlette.requests import Request +from starlette.responses import Response from task_registry.api.main import api_router from task_registry.core.config import LICENSE_NAME, LICENSE_URL, PROJECT_NAME, PROJECT_SUMMARY, VERSION from task_registry.lifespan import lifespan +# Register process metrics collector (provides CPU, memory stats on Linux) +ProcessCollector(registry=REGISTRY) + def create_app() -> FastAPI: app = FastAPI( @@ -14,6 +22,16 @@ def create_app() -> FastAPI: docs_url="/", ) app.include_router(api_router) + + Instrumentator(excluded_handlers=["/metrics", "/health"]).instrument(app) + + # TODO(security): /metrics and /health endpoints are currently exposed on the main port. + # For production, consider running these on a separate internal port (e.g., 8001) to avoid + # public exposure, or ensure they are blocked at the Ingress level. + @app.get("/metrics", include_in_schema=False) + async def metrics(_request: Request) -> Response: # pyright: ignore[reportUnusedFunction] + return Response(content=generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST) + return app diff --git a/tests/api/routes/test_metrics.py b/tests/api/routes/test_metrics.py new file mode 100644 index 0000000..c6700d8 --- /dev/null +++ b/tests/api/routes/test_metrics.py @@ -0,0 +1,11 @@ +from fastapi.testclient import TestClient + + +def test_metrics_endpoint(client: TestClient) -> None: + response = client.get("/metrics") + assert response.status_code == 200 + assert "text/plain" in response.headers["content-type"] + # Check for expected Prometheus metrics + assert "http_requests_total" in response.text + assert "http_request_duration_seconds" in response.text + assert "python_info" in response.text From d62d2152beed4a07449811d8a9afaade9771af8c Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Fri, 19 Dec 2025 11:34:29 +0100 Subject: [PATCH 8/9] Fix import error for prometheus statistics --- task_registry/server.py | 4 ---- tests/api/routes/test_metrics.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/task_registry/server.py b/task_registry/server.py index 9e2509c..17f1bbf 100644 --- a/task_registry/server.py +++ b/task_registry/server.py @@ -1,6 +1,5 @@ from fastapi import FastAPI from prometheus_client import CONTENT_TYPE_LATEST, REGISTRY, generate_latest -from prometheus_client.process_collector import ProcessCollector from prometheus_fastapi_instrumentator import Instrumentator from starlette.requests import Request from starlette.responses import Response @@ -8,9 +7,6 @@ from task_registry.core.config import LICENSE_NAME, LICENSE_URL, PROJECT_NAME, PROJECT_SUMMARY, VERSION from task_registry.lifespan import lifespan -# Register process metrics collector (provides CPU, memory stats on Linux) -ProcessCollector(registry=REGISTRY) - def create_app() -> FastAPI: app = FastAPI( diff --git a/tests/api/routes/test_metrics.py b/tests/api/routes/test_metrics.py index c6700d8..9b919e6 100644 --- a/tests/api/routes/test_metrics.py +++ b/tests/api/routes/test_metrics.py @@ -1,8 +1,19 @@ +import pytest from fastapi.testclient import TestClient def test_metrics_endpoint(client: TestClient) -> None: - response = client.get("/metrics") + try: + response = client.get("/metrics") + except TypeError as e: + # Known issue: prometheus_client ProcessCollector has a bug on some Linux environments + # (e.g., GitHub Actions runners) where it fails with "must be str or None, not bytes" + # when reading /proc/[pid]/stat. This works correctly in Kubernetes clusters. + # See: https://github.com/prometheus/client_python/issues + if "must be str or None, not bytes" in str(e): + pytest.skip("Skipping due to known ProcessCollector bug on this platform") + raise + assert response.status_code == 200 assert "text/plain" in response.headers["content-type"] # Check for expected Prometheus metrics From 413798aa91b821fb57a63e20a2889d29404d22b6 Mon Sep 17 00:00:00 2001 From: robbertuittenbroek Date: Mon, 2 Feb 2026 09:21:28 +0100 Subject: [PATCH 9/9] Fixed deployment links --- README.md | 2 +- task_registry/data.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cb6b0e5..976507c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This registry is an overarching architecture to group all Tasks that can be performed in the context of algorithm and AI-system management, compliance and governance. The purpose of the register is to bridge policy and running code. A task could be part of an instrument and correspond to a maatregel/measure and/or vereisten/requirement (see 2. Definitions). -The registry is deployed here: [https://task-registry.apps.digilab.network/](https://task-registry.apps.digilab.network/). +The registry is deployed here: [https://task-registry.rijksapp.nl/](https://task-registry.rijksapp.nl/). A graphical overview of the task registry and its components is shown below: diff --git a/task_registry/data.py b/task_registry/data.py index 1a25e8a..e261680 100644 --- a/task_registry/data.py +++ b/task_registry/data.py @@ -34,8 +34,8 @@ class Index(BaseModel): size: int = Field(examples=[0]) name: str = Field(examples=["task_collection_name"]) path: str = Field(examples=["task_collection_path"]) - download_url: str = Field(examples=["https://task-registry.apps.digilab.network/task_collection"]) - links: Link = Field(examples=[{"self": "https://task-registry.apps.digilab.network"}]) + download_url: str = Field(examples=["https://task-registry.rijksapp.nl/task_collection"]) + links: Link = Field(examples=[{"self": "https://task-registry.rijksapp.nl"}]) entries: list[FileInfo] = Field( examples=[ { @@ -44,10 +44,8 @@ class Index(BaseModel): "name": "task_name.yaml", "path": "task_collection_path/task_name.yaml", "urn": "urn:nl:aivt:tr:xx:xx", - "download_url": "https://task-registry.apps.digilab.network/task_collection/urn/urn:nl:aivt:tr:aiia:1.0", - "links": { - "self": "https://task-registry.apps.digilab.network/task_collection/urn/urn:nl:aivt:tr:aiia:1.0" - }, + "download_url": "https://task-registry.rijksapp.nl/task_collection/urn/urn:nl:aivt:tr:aiia:1.0", + "links": {"self": "https://task-registry.rijksapp.nl/task_collection/urn/urn:nl:aivt:tr:aiia:1.0"}, } ] ) @@ -83,7 +81,7 @@ def has_urn(self, urn: str, tasks: TaskType) -> bool: def generate_index( tasks: TaskType, - base_url: str = "https://task-registry.apps.digilab.network", + base_url: str = "https://task-registry.rijksapp.nl", ) -> Index: tasks_url = f"{base_url}/{tasks}" entries: list[FileInfo] = []