Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
# Only modify to add/remove expected env keys.
# Create a new .env file containing the variables below and replace '{VALUE_NAME}' with the specific value or key.
# DO NOT COMMIT YOUR .env FILE TO SOURCE CONTROL (.gitignore should handle that)
OPENAI_API_KEY={YOUR_API_KEY}
FEATURE_DEMO_MODE=false
DEMO_GENAI_MODEL_NAME={MODEL_NAME}
DEMO_GENAI_API_KEY={MODEL_VERSION}
DEMO_GENAI_PROVIDER={GENAI_PROVIDER}
ENABLE_DEBUG_MODE=false
14 changes: 14 additions & 0 deletions app/feature_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os
from dataclasses import dataclass, field


@dataclass(frozen=True, repr=True)
class FeatureFlags:
"""
Container to manage feature flags for the application.
"""

demo_mode: bool = field(
default_factory=lambda: os.getenv("FEATURE_DEMO_MODE", "false").lower()
== "true"
)
76 changes: 54 additions & 22 deletions app/settings/settings_repo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Optional
import os
from feature_flags import FeatureFlags
import tomllib
import pathlib
from dataclasses import dataclass
Expand Down Expand Up @@ -37,6 +39,28 @@ class SettingsData:
_current_settings: Optional[SettingsData] = None


def __load_settings_file(custom_path: str = None) -> dict:
"""
Load settings from a TOML file and return the settings dictionary.

Args:
custom_path (str): Path to the TOML file. Defaults to "settings.toml" if not provided.

Returns:
dict: Settings loaded from the TOML file.
"""
path = "./settings.toml"
if custom_path:
path = custom_path

file = pathlib.Path(path)

with open(file, "rb") as f:
settings = tomllib.load(f)

return settings


def load_settings(
custom_path: str = None, reload_settings: bool = False
) -> SettingsData:
Expand All @@ -60,55 +84,63 @@ def load_settings(
if (_current_settings) and (not reload_settings):
return _current_settings

# If custom path is provided, use it
path = "./settings.toml"
if custom_path:
path = custom_path

file = pathlib.Path(path)

with open(file, "rb") as f:
settings = tomllib.load(f)
selected_engine: str
model: str
api_key: str
is_debug_mode: bool = os.getenv("ENABLE_DEBUG_MODE", "false").lower() == "true"
feature_flags = FeatureFlags()

if feature_flags.demo_mode:
selected_engine = os.getenv("DEMO_GENAI_PROVIDER")
api_key = os.getenv("DEMO_GENAI_API_KEY")
model = os.getenv("DEMO_GENAI_MODEL_NAME")
else:
# If custom path is provided, use it
path = "./settings.toml"
if custom_path:
path = custom_path

# Load settings from the TOML file
settings = __load_settings_file(path)
selected_engine = settings["selected_ocr_engine"]
api_key = settings.get(selected_engine, {}).get("api_key", "")
model = settings.get(selected_engine, {}).get("model", "")
is_debug_mode = settings.get("debug_mode", False)

selected_engine = settings["selected_ocr_engine"]
engine_config = settings.get(selected_engine)
is_debug_mode = settings.get("debug_mode", False)
enable_debug_logging(is_debug_mode)

match selected_engine:
case "open_ai":
_current_settings = SettingsData(
selected_config=OpenAiConfig(
api_key=engine_config["api_key"],
model=engine_config["model"],
api_key=api_key,
model=model,
)
)
case "mistral_ai":
_current_settings = SettingsData(
selected_config=MistralAiConfig(
api_key=engine_config["api_key"],
model=engine_config["model"],
api_key=api_key,
model=model,
)
)
case "gemini_ai":
_current_settings = SettingsData(
selected_config=GeminiAiConfig(
api_key=engine_config["api_key"],
model=engine_config["model"],
api_key=api_key,
model=model,
)
)
case _:
raise ValueError(
f"Could not find configuration for {selected_engine}. Please check your settings file."
)

_current_settings.debug_mode = settings.get("debug_mode", False)
_current_settings.debug_mode = is_debug_mode

logger.debug(f"Loaded settings: {_current_settings}")
logger.info(
"Selected OCR engine {x} with model {y}:".format(
x=selected_engine, y=engine_config["model"]
)
"Selected OCR engine {x} with model {y}:".format(x=selected_engine, y=model)
)

return _current_settings
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dev = [
"flake8>=7.2.0",
"pytest>=8.3.5",
"pytest-cov>=6.1.1",
"pytest-env>=1.1.5",
"ruff>=0.11.4",
]

Expand Down
61 changes: 61 additions & 0 deletions tests/test_settings_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import pytest
from app.settings import load_settings, OpenAiConfig, MistralAiConfig, GeminiAiConfig


def test_error_when_demo_active_without_genai_variables(monkeypatch):
monkeypatch.setenv("FEATURE_DEMO_MODE", "true")
with pytest.raises(ValueError) as exc_info:
load_settings(reload_settings=True)

assert (
"Could not find configuration for None. Please check your settings file."
in str(exc_info.value)
)


def test_env_mistral_selected_config(monkeypatch):
monkeypatch.setenv("FEATURE_DEMO_MODE", "true")
monkeypatch.setenv("DEMO_GENAI_PROVIDER", "mistral_ai")
monkeypatch.setenv("DEMO_GENAI_API_KEY", "Your Mistral API key")
monkeypatch.setenv("DEMO_GENAI_MODEL_NAME", "default")

settings = load_settings(reload_settings=True)
assert settings.selected_config == MistralAiConfig(
api_key="Your Mistral API key", model="default"
)


def test_env_openai_selected_config(monkeypatch):
monkeypatch.setenv("FEATURE_DEMO_MODE", "true")
monkeypatch.setenv("DEMO_GENAI_PROVIDER", "open_ai")
monkeypatch.setenv("DEMO_GENAI_API_KEY", "Your Open AI API key")
monkeypatch.setenv("DEMO_GENAI_MODEL_NAME", "default")

settings = load_settings(reload_settings=True)
assert settings.selected_config == OpenAiConfig(
api_key="Your Open AI API key", model="default"
)


def test_env_gemini_selected_config(monkeypatch):
monkeypatch.setenv("FEATURE_DEMO_MODE", "true")
monkeypatch.setenv("DEMO_GENAI_PROVIDER", "gemini_ai")
monkeypatch.setenv("DEMO_GENAI_API_KEY", "Your Gemini AI API key")
monkeypatch.setenv("DEMO_GENAI_MODEL_NAME", "default")

settings = load_settings(reload_settings=True)
assert settings.selected_config == GeminiAiConfig(
api_key="Your Gemini AI API key", model="default"
)


def test_env_debug_mode_enabled(monkeypatch):

monkeypatch.setenv("ENABLE_DEBUG_MODE", "true")
monkeypatch.setenv("FEATURE_DEMO_MODE", "true")
monkeypatch.setenv("DEMO_GENAI_PROVIDER", "gemini_ai")
monkeypatch.setenv("DEMO_GENAI_API_KEY", "Your Gemini AI API key")
monkeypatch.setenv("DEMO_GENAI_MODEL_NAME", "default")

settings = load_settings(reload_settings=True)
assert settings.debug_mode is True
Loading