From 6c8e6b4b0ad84721f5f1614295a175dd494963b3 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Tue, 3 Feb 2026 16:07:40 -0500 Subject: [PATCH] Implement configuration loader module This commit implements a robust configuration loader using Pydantic Settings that handles: Features: - Environment variable loading with .env file support - Type validation and field constraints - Gitea configuration (URL, token, owner, repo) - HTTP server configuration (host, port) - Optional HTTP authentication token - Optional tool filtering (enabled/disabled tool lists) Implementation: - GiteaSettings class with Pydantic validation - URL validation ensuring http:// or https:// prefix - Helper properties for parsing comma-separated tool lists - get_gitea_mcp_env() method to pass config to wrapped MCP server - load_settings() factory function with optional env_file path Documentation: - .env.example template with all configuration options - Comprehensive docstrings and type hints This module unblocks both the tool filtering (#12) and HTTP authentication middleware (#13) implementations. Closes #11 Co-Authored-By: Claude Opus 4.5 --- .env.example | 16 +++ src/gitea_http_wrapper/config/__init__.py | 4 +- src/gitea_http_wrapper/config/settings.py | 113 ++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 src/gitea_http_wrapper/config/settings.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6639b1d --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Gitea Configuration +GITEA_URL=https://gitea.example.com +GITEA_TOKEN=your_gitea_api_token_here +GITEA_OWNER=your_username_or_org +GITEA_REPO=your_repo_name + +# HTTP Server Configuration +HTTP_HOST=127.0.0.1 +HTTP_PORT=8000 + +# Authentication Configuration (Optional) +# AUTH_TOKEN=your_bearer_token_here + +# Tool Filtering Configuration (Optional) +# ENABLED_TOOLS=list_issues,create_issue,update_issue +# DISABLED_TOOLS=delete_issue,close_milestone diff --git a/src/gitea_http_wrapper/config/__init__.py b/src/gitea_http_wrapper/config/__init__.py index 42f952f..9f9ee4a 100644 --- a/src/gitea_http_wrapper/config/__init__.py +++ b/src/gitea_http_wrapper/config/__init__.py @@ -1,3 +1,5 @@ """Configuration loader module.""" -__all__ = [] +from .settings import GiteaSettings, load_settings + +__all__ = ["GiteaSettings", "load_settings"] diff --git a/src/gitea_http_wrapper/config/settings.py b/src/gitea_http_wrapper/config/settings.py new file mode 100644 index 0000000..5dd7374 --- /dev/null +++ b/src/gitea_http_wrapper/config/settings.py @@ -0,0 +1,113 @@ +"""Configuration settings for Gitea HTTP MCP wrapper.""" + +from pathlib import Path +from typing import Optional + +from pydantic import Field, field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class GiteaSettings(BaseSettings): + """Configuration settings loaded from environment or .env file.""" + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + extra="ignore", + ) + + # Gitea Configuration + gitea_url: str = Field( + ..., + description="Gitea instance URL (e.g., https://git.example.com)", + ) + gitea_token: str = Field( + ..., + description="Gitea API token for authentication", + ) + gitea_owner: str = Field( + ..., + description="Default repository owner/organization", + ) + gitea_repo: str = Field( + ..., + description="Default repository name", + ) + + # HTTP Server Configuration + http_host: str = Field( + default="127.0.0.1", + description="HTTP server bind address", + ) + http_port: int = Field( + default=8000, + ge=1, + le=65535, + description="HTTP server port", + ) + + # Authentication Configuration + auth_token: Optional[str] = Field( + default=None, + description="Bearer token for HTTP authentication (optional)", + ) + + # Tool Filtering Configuration + enabled_tools: Optional[str] = Field( + default=None, + description="Comma-separated list of enabled tools (optional, enables all if not set)", + ) + disabled_tools: Optional[str] = Field( + default=None, + description="Comma-separated list of disabled tools (optional)", + ) + + @field_validator("gitea_url") + @classmethod + def validate_gitea_url(cls, v: str) -> str: + """Ensure Gitea URL is properly formatted.""" + if not v.startswith(("http://", "https://")): + raise ValueError("gitea_url must start with http:// or https://") + return v.rstrip("/") + + @property + def enabled_tools_list(self) -> Optional[list[str]]: + """Parse enabled_tools into a list.""" + if not self.enabled_tools: + return None + return [tool.strip() for tool in self.enabled_tools.split(",") if tool.strip()] + + @property + def disabled_tools_list(self) -> Optional[list[str]]: + """Parse disabled_tools into a list.""" + if not self.disabled_tools: + return None + return [tool.strip() for tool in self.disabled_tools.split(",") if tool.strip()] + + def get_gitea_mcp_env(self) -> dict[str, str]: + """Get environment variables for the wrapped Gitea MCP server.""" + return { + "GITEA_BASE_URL": self.gitea_url, + "GITEA_API_TOKEN": self.gitea_token, + "GITEA_DEFAULT_OWNER": self.gitea_owner, + "GITEA_DEFAULT_REPO": self.gitea_repo, + } + + +def load_settings(env_file: Optional[Path] = None) -> GiteaSettings: + """ + Load settings from environment or .env file. + + Args: + env_file: Optional path to .env file. If not provided, searches for .env in current directory. + + Returns: + GiteaSettings instance with loaded configuration. + + Raises: + ValidationError: If required settings are missing or invalid. + """ + if env_file: + return GiteaSettings(_env_file=env_file) + return GiteaSettings()