From 7563b7eaaf0a92b3bf2147e44d1bbcbc6b87211a Mon Sep 17 00:00:00 2001 From: sami-m-g Date: Wed, 26 Jun 2024 14:16:48 +0300 Subject: [PATCH 1/4] Added settings.py --- alembic/env.py | 6 +++--- dds_glossary/__init__.py | 4 ---- dds_glossary/auth.py | 5 +++-- dds_glossary/main.py | 6 +++--- dds_glossary/settings.py | 22 ++++++++++++++++++++++ pyproject.toml | 2 +- 6 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 dds_glossary/settings.py diff --git a/alembic/env.py b/alembic/env.py index 516c8d6..8cc93ef 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,13 +1,13 @@ """Alembic environment configuration.""" from logging.config import fileConfig -from os import getenv as os_getenv from sqlalchemy import create_engine from sqlalchemy.pool import NullPool from alembic import context from dds_glossary.model import Base +from dds_glossary.settings import get_settings # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -20,9 +20,9 @@ # add your model's MetaData object here # for 'autogenerate' support +database_url = get_settings().DATABASE_URL.get_secret_value() target_metadata = Base.metadata -from dds_glossary.database import DATABASE_URL -config.set_main_option("sqlalchemy.url", DATABASE_URL) +config.set_main_option("sqlalchemy.url", database_url) # other values from the config, defined by the needs of env.py, diff --git a/dds_glossary/__init__.py b/dds_glossary/__init__.py index da2ae75..2811571 100644 --- a/dds_glossary/__init__.py +++ b/dds_glossary/__init__.py @@ -1,9 +1,5 @@ """dds_glossary package.""" -from dotenv import load_dotenv - __version__ = "0.1.0" __all__ = ["__version__"] - -load_dotenv() diff --git a/dds_glossary/auth.py b/dds_glossary/auth.py index 7166b56..5bc5095 100644 --- a/dds_glossary/auth.py +++ b/dds_glossary/auth.py @@ -1,11 +1,12 @@ """Authentication utils for the dds_glossary package.""" from http import HTTPStatus -from os import getenv as os_getenv from fastapi import Depends, HTTPException from fastapi.security import APIKeyHeader +from .settings import get_settings + api_key_header = APIKeyHeader(name="X-API-Key") @@ -23,7 +24,7 @@ def get_api_key(api_key: str = Depends(api_key_header)) -> dict[str, str]: HTTPException: If the API key is missing or invalid. Or if the API key environment variable is missing. """ - correct_api_key: str | None = os_getenv("API_KEY") + correct_api_key: str | None = get_settings().API_KEY.get_secret_value() if correct_api_key is None or api_key != correct_api_key: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, diff --git a/dds_glossary/main.py b/dds_glossary/main.py index c6ac063..b0e0052 100644 --- a/dds_glossary/main.py +++ b/dds_glossary/main.py @@ -1,7 +1,6 @@ """Main entry for the dds_glossary server.""" import argparse -from os import getenv as os_getenv import sentry_sdk import uvicorn @@ -9,6 +8,7 @@ from fastapi_versioning import VersionedFastAPI from .routes import router_non_versioned, router_versioned +from .settings import get_settings def create_app() -> FastAPI: @@ -17,7 +17,7 @@ def create_app() -> FastAPI: FastAPI: the application object """ sentry_sdk.init( - dsn=os_getenv("SENTRY_DSN"), + dsn=get_settings().SENTRY_DSN.get_secret_value(), # Set traces_sample_rate to 1.0 to capture 100% # of transactions for performance monitoring. traces_sample_rate=1.0, @@ -48,5 +48,5 @@ def create_app() -> FastAPI: uvicorn.run( "dds_glossary.main:create_app", reload=args.f_reload, - host=os_getenv("HOST_IP", "127.0.0.1"), + host=get_settings().HOST_IP, ) diff --git a/dds_glossary/settings.py b/dds_glossary/settings.py new file mode 100644 index 0000000..52c627e --- /dev/null +++ b/dds_glossary/settings.py @@ -0,0 +1,22 @@ +"""Settings for dds_glossary package.""" + +from functools import lru_cache + +from pydantic import SecretStr +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + """Settings for dds_glossary package.""" + + API_KEY: SecretStr + DATABASE_URL: SecretStr + SENTRY_DSN: SecretStr + + HOST_IP: str = "127.0.0.1" + + +@lru_cache() +def get_settings(): + """Returns settings object.""" + return Settings() diff --git a/pyproject.toml b/pyproject.toml index b916a01..7c93e74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "lxml", "owlready2", "psycopg", - "python-dotenv", + "pydantic_settings", "sentry-sdk[fastapi]", "SQLAlchemy>=2.0.0", "sqlalchemy-utils", From 6a6ffe1f2d08e1270d2defb1c3ddb5dfff095634 Mon Sep 17 00:00:00 2001 From: sami-m-g Date: Wed, 26 Jun 2024 14:42:38 +0300 Subject: [PATCH 2/4] dds_glossary/settings.py: set default value for SENTRY_DSN --- dds_glossary/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_glossary/settings.py b/dds_glossary/settings.py index 52c627e..c568b76 100644 --- a/dds_glossary/settings.py +++ b/dds_glossary/settings.py @@ -11,7 +11,7 @@ class Settings(BaseSettings): API_KEY: SecretStr DATABASE_URL: SecretStr - SENTRY_DSN: SecretStr + SENTRY_DSN: SecretStr = SecretStr("") HOST_IP: str = "127.0.0.1" From 7151f4f2597aae6934018782cbdb9ae4c21adc4d Mon Sep 17 00:00:00 2001 From: sami-m-g Date: Wed, 26 Jun 2024 14:45:01 +0300 Subject: [PATCH 3/4] Resolved API_KEY in tests --- tests/unit/test_auth.py | 8 ++++---- tests/unit/test_routes.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 9c124f4..615c7b6 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -7,6 +7,7 @@ from pytest import raises as pytest_raises from dds_glossary.auth import get_api_key +from dds_glossary.settings import get_settings def _validate_error(exc_info: ExceptionInfo) -> None: @@ -36,8 +37,7 @@ def test_get_api_key_invalid_key() -> None: _validate_error(exc_info) -def test_get_api_key_valid_key(monkeypatch: MonkeyPatch) -> None: +def test_get_api_key_valid_key() -> None: """Test the get_api_key function with a valid key.""" - api_key = "valid" - monkeypatch.setenv("API_KEY", api_key) - assert get_api_key(api_key="valid") == {"api_key": api_key} + api_key = get_settings().API_KEY.get_secret_value() + assert get_api_key(api_key=api_key) == {"api_key": api_key} diff --git a/tests/unit/test_routes.py b/tests/unit/test_routes.py index e0aade2..9644dc0 100644 --- a/tests/unit/test_routes.py +++ b/tests/unit/test_routes.py @@ -7,6 +7,7 @@ from dds_glossary.model import Dataset, FailedDataset from dds_glossary.schema import InitDatasetsResponse, VersionResponse +from dds_glossary.settings import get_settings def test_version( @@ -53,9 +54,8 @@ def test_init_datasets_valid_key(client: TestClient, monkeypatch: MonkeyPatch) - failed_datasets=failed_datasets, ), ) - api_key = "valid" - monkeypatch.setenv("API_KEY", api_key) + api_key = get_settings().API_KEY.get_secret_value() response = client.post("/latest/init_datasets", headers={"X-API-Key": api_key}) assert response.status_code == HTTPStatus.OK assert response.headers["content-type"] == "application/json" From e0059bbf8d75e50ab209fd4b418252869e437846 Mon Sep 17 00:00:00 2001 From: sami-m-g Date: Wed, 26 Jun 2024 14:48:22 +0300 Subject: [PATCH 4/4] pyproject.toml: pin pydantic_settings to current major --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c93e74..23fc09a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "lxml", "owlready2", "psycopg", - "pydantic_settings", + "pydantic_settings~=2.0", "sentry-sdk[fastapi]", "SQLAlchemy>=2.0.0", "sqlalchemy-utils",