diff --git a/.github/workflows/stage-2-test.yaml b/.github/workflows/stage-2-test.yaml index 13490015..86af1bd1 100644 --- a/.github/workflows/stage-2-test.yaml +++ b/.github/workflows/stage-2-test.yaml @@ -50,7 +50,7 @@ jobs: - name: "Save the coverage check result" uses: actions/upload-artifact@v4 with: - name: coverage + name: coverage.xml path: coverage.xml test-lint: name: "Linting" @@ -85,7 +85,7 @@ jobs: - name: "Get the coverage report" uses: actions/download-artifact@v4 with: - name: coverage + name: coverage.xml - name: "Perform static analysis" uses: ./.github/actions/perform-static-analysis with: diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml index df628f7f..15af0116 100644 --- a/.github/workflows/stage-4-acceptance.yaml +++ b/.github/workflows/stage-4-acceptance.yaml @@ -111,7 +111,7 @@ jobs: needs: environment-set-up timeout-minutes: 10 env: - LOCALSTACK_INTERNAL_DYNAMODB_ENDPOINT: http://localstack:4566/ + LOCALSTACK_INTERNAL_ENDPOINT: http://localstack:4566/ steps: - name: "Checkout code" uses: actions/checkout@v4 diff --git a/README.md b/README.md index c8e71371..37a29553 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,14 @@ make precommit There are `make` tasks for you to configure to run your tests. Run `make test` to see how they work. You should be able to use the same entry points for local development as in your CI pipeline. +## Conflict with yanai + +If you have previously built [yanai](https://nhsd-confluence.digital.nhs.uk/pages/viewpage.action?pageId=48826732), which is the platform we use to supply data to this project, that uses an old version of localstack that does not support our Python version. We have pinned the correct version here and yanai have their version pinned as well so it should work fine, but sometimes issues can arise - if so then removing the docker image can solve that, before then rebuilding. + +```shell + docker rmi localstack/localstack +``` + ## Design We'll be separating our [presentation](https://martinfowler.com/eaaDev/SeparatedPresentation.html) layer (where API logic lives, in [`views/`](src/eligibility_signposting_api/views)), [business services](https://martinfowler.com/eaaCatalog/serviceLayer.html) layer (where business logic lives, in [`services/`](src/eligibility_signposting_api/services)) and [repository](https://martinfowler.com/eaaCatalog/repository.html) layer (where database logic lives, [`repos/`](src/eligibility_signposting_api/repos)). We will be using [wireup](https://pypi.org/project/wireup/) for [dependency injection](https://pinboard.in/u:brunns/t:dependency-injection), so services get their dependencies given to them ("injection"), and wireup takes care of that. (We'll usually use the [`@service` annotation](https://maldoinc.github.io/wireup/latest/services/), but [factory functions](https://maldoinc.github.io/wireup/latest/factory_functions/) will be used where necessary, typically for creating resources from 3rd party libraries.) We'll be using [Pydantic](https://pypi.org/project/pydantic/) for both response models and database models. diff --git a/pyproject.toml b/pyproject.toml index cb5a80d5..def4f287 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ pytest-docker = "^3.2.0" stamina = "^25.1.0" [tool.poetry-plugin-lambda-build] -docker-image = "public.ecr.aws/sam/build-python3.13:latest-x86_64" +docker-image = "public.ecr.aws/sam/build-python3.13:1.135-x86_64" docker-network = "host" docker-platform = "linux/x86_64" package-artifact-path = "dist/lambda.zip" @@ -83,17 +83,9 @@ log_format = "%(asctime)s %(levelname)s %(message)s" log_date_format = "%Y-%m-%d %H:%M:%S" [tool.coverage.run] +relative_files = true branch = true -source = ["src/eligibility_signposting_api"] -omit = [ - "tests/*", - "*/__init__.py", - "*/migrations/*", - "*/settings/*", - "*/venv/*", - "*/.venv/*", - "*/site-packages/*", -] +source = ["src"] [tool.coverage.report] show_missing = true diff --git a/scripts/config/sonar-scanner.properties b/scripts/config/sonar-scanner.properties index 055154d2..a367ecc3 100644 --- a/scripts/config/sonar-scanner.properties +++ b/scripts/config/sonar-scanner.properties @@ -4,15 +4,12 @@ sonar.host.url=https://sonarcloud.io sonar.qualitygate.wait=true sonar.sourceEncoding=UTF-8 -#sonar.python.coverage.reportPaths=.coverage/coverage.xml -#sonar.[javascript|typescript].lcov.reportPaths=.coverage/lcov.info - sonar.python.version=3.13 -sonar.sources=src/ +sonar.sources=src sonar.exclusions=**/test_*.py -sonar.tests=tests/ +sonar.tests=tests sonar.test.inclusions=**/test_*.py sonar.python.coverage.reportPaths=coverage.xml diff --git a/scripts/config/vale/styles/config/vocabularies/words/accept.txt b/scripts/config/vale/styles/config/vocabularies/words/accept.txt index 9511ae7a..cae10a15 100644 --- a/scripts/config/vale/styles/config/vocabularies/words/accept.txt +++ b/scripts/config/vale/styles/config/vocabularies/words/accept.txt @@ -21,3 +21,4 @@ pyenv colima wireup Pydantic +yanai diff --git a/scripts/tests/lint.sh b/scripts/tests/lint.sh new file mode 100755 index 00000000..161ef1c2 --- /dev/null +++ b/scripts/tests/lint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +make dependencies install-python lint diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh index a677ba03..5764005e 100755 --- a/scripts/tests/unit.sh +++ b/scripts/tests/unit.sh @@ -18,5 +18,4 @@ cd "$(git rev-parse --show-toplevel)" # tasks in scripts/test.mk. make dependencies install-python -poetry run pytest tests/unit/ --durations=10 --cov-report= --cov src/ -poetry run python -m coverage xml +poetry run pytest tests/unit/ --durations=10 --cov-report=xml --cov=src/ diff --git a/src/eligibility_signposting_api/app.py b/src/eligibility_signposting_api/app.py index 0bfcbf84..9c7070c9 100644 --- a/src/eligibility_signposting_api/app.py +++ b/src/eligibility_signposting_api/app.py @@ -13,6 +13,9 @@ from eligibility_signposting_api.views.eligibility import eligibility from eligibility_signposting_api.views.hello import hello +init_logging() +logger = logging.getLogger(__name__) + def main() -> None: # pragma: no cover """Run the Flask app as a local process.""" @@ -27,10 +30,8 @@ def lambda_handler(event: LambdaEvent, context: LambdaContext) -> dict[str, Any] def create_app() -> Flask: - init_logging() - app = Flask(__name__) - app.logger.info("app created") + logger.info("app created") # Register views & error handler app.register_blueprint(eligibility, url_prefix="/eligibility") @@ -41,7 +42,7 @@ def create_app() -> Flask: container = wireup.create_container(service_modules=[services, repos], parameters=config()) wireup.integration.flask.setup(container, app, import_flask_config=True) - app.logger.info("app ready") + logger.info("app ready") return app diff --git a/src/eligibility_signposting_api/config.py b/src/eligibility_signposting_api/config.py index 78a43693..1b4aa414 100644 --- a/src/eligibility_signposting_api/config.py +++ b/src/eligibility_signposting_api/config.py @@ -1,11 +1,10 @@ import logging import os -from logging.config import dictConfig from typing import Any, NewType from yarl import URL -LOG_LEVEL = logging.DEBUG +LOG_LEVEL = logging.getLevelNamesMapping().get(os.getenv("LOG_LEVEL", ""), logging.WARNING) AwsRegion = NewType("AwsRegion", str) AwsAccessKey = NewType("AwsAccessKey", str) @@ -22,22 +21,5 @@ def config() -> dict[str, Any]: def init_logging() -> None: - level = logging.getLevelName(LOG_LEVEL) log_format = "%(asctime)s %(levelname)-8s %(name)s %(module)s.py:%(funcName)s():%(lineno)d %(message)s" - dictConfig( - { - "version": 1, - "formatters": { - "default": { - "format": log_format, - } - }, - "handlers": { - "wsgi": {"class": "logging.StreamHandler", "stream": "ext://sys.stdout", "formatter": "default"} - }, - "root": {"level": level, "handlers": ["wsgi"]}, - "loggers": { - "eligibility_signposting_api.app": {"level": level, "handlers": ["wsgi"], "propagate": False}, - }, - } - ) + logging.basicConfig(level=LOG_LEVEL, format=log_format, handlers=[logging.StreamHandler()]) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index c9f8308b..36bcdc81 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,7 +1,7 @@ services: localstack: # container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}" - image: localstack/localstack + image: localstack/localstack:4.2.0 ports: - "4566:4566" # LocalStack Gateway - "4510-4559:4510-4559" # external services port range diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f7441bf0..797b7e22 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -92,9 +92,7 @@ def flask_function(lambda_client: BaseClient) -> str: Timeout=180, Environment={ "Variables": { - "DYNAMODB_ENDPOINT": os.getenv( - "LOCALSTACK_INTERNAL_DYNAMODB_ENDPOINT", "http://host.docker.internal:4566" - ), + "DYNAMODB_ENDPOINT": os.getenv("LOCALSTACK_INTERNAL_ENDPOINT", "http://localstack:4566/"), "AWS_REGION": AWS_REGION, } },