Initial commit
Some checks failed
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
This commit is contained in:
41
libs/config/__init__.py
Normal file
41
libs/config/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Configuration management and client factories."""
|
||||
|
||||
from .factories import (
|
||||
EventBusFactory,
|
||||
MinIOClientFactory,
|
||||
Neo4jDriverFactory,
|
||||
QdrantClientFactory,
|
||||
RedisClientFactory,
|
||||
VaultClientFactory,
|
||||
)
|
||||
from .settings import BaseAppSettings
|
||||
from .utils import (
|
||||
create_event_bus,
|
||||
create_minio_client,
|
||||
create_neo4j_client,
|
||||
create_qdrant_client,
|
||||
create_redis_client,
|
||||
create_vault_client,
|
||||
get_default_settings,
|
||||
get_settings,
|
||||
init_settings,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BaseAppSettings",
|
||||
"VaultClientFactory",
|
||||
"MinIOClientFactory",
|
||||
"QdrantClientFactory",
|
||||
"Neo4jDriverFactory",
|
||||
"RedisClientFactory",
|
||||
"EventBusFactory",
|
||||
"get_settings",
|
||||
"init_settings",
|
||||
"create_vault_client",
|
||||
"create_minio_client",
|
||||
"create_qdrant_client",
|
||||
"create_neo4j_client",
|
||||
"create_redis_client",
|
||||
"create_event_bus",
|
||||
"get_default_settings",
|
||||
]
|
||||
122
libs/config/factories.py
Normal file
122
libs/config/factories.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Client factories for various services."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import boto3 # type: ignore
|
||||
import hvac
|
||||
import redis.asyncio as redis
|
||||
from aiokafka import AIOKafkaConsumer, AIOKafkaProducer # type: ignore
|
||||
from minio import Minio
|
||||
from neo4j import GraphDatabase
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
from .settings import BaseAppSettings
|
||||
|
||||
|
||||
class VaultClientFactory: # pylint: disable=too-few-public-methods
|
||||
"""Factory for creating Vault clients"""
|
||||
|
||||
@staticmethod
|
||||
def create_client(settings: BaseAppSettings) -> hvac.Client:
|
||||
"""Create authenticated Vault client"""
|
||||
client = hvac.Client(url=settings.vault_addr)
|
||||
|
||||
if settings.vault_token:
|
||||
# Development mode with token
|
||||
client.token = settings.vault_token
|
||||
elif settings.vault_role_id and settings.vault_secret_id:
|
||||
# Production mode with AppRole
|
||||
try:
|
||||
auth_response = client.auth.approle.login(
|
||||
role_id=settings.vault_role_id, secret_id=settings.vault_secret_id
|
||||
)
|
||||
client.token = auth_response["auth"]["client_token"]
|
||||
except Exception as e:
|
||||
raise ValueError("Failed to authenticate with Vault") from e
|
||||
else:
|
||||
raise ValueError(
|
||||
"Either vault_token or vault_role_id/vault_secret_id must be provided"
|
||||
)
|
||||
|
||||
if not client.is_authenticated():
|
||||
raise ValueError("Failed to authenticate with Vault")
|
||||
|
||||
return client
|
||||
|
||||
|
||||
class MinIOClientFactory: # pylint: disable=too-few-public-methods
|
||||
"""Factory for creating MinIO clients"""
|
||||
|
||||
@staticmethod
|
||||
def create_client(settings: BaseAppSettings) -> Minio:
|
||||
"""Create MinIO client"""
|
||||
return Minio(
|
||||
endpoint=settings.minio_endpoint,
|
||||
access_key=settings.minio_access_key,
|
||||
secret_key=settings.minio_secret_key,
|
||||
secure=settings.minio_secure,
|
||||
)
|
||||
|
||||
|
||||
class QdrantClientFactory: # pylint: disable=too-few-public-methods
|
||||
"""Factory for creating Qdrant clients"""
|
||||
|
||||
@staticmethod
|
||||
def create_client(settings: BaseAppSettings) -> QdrantClient:
|
||||
"""Create Qdrant client"""
|
||||
return QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
||||
|
||||
|
||||
class Neo4jDriverFactory: # pylint: disable=too-few-public-methods
|
||||
"""Factory for creating Neo4j drivers"""
|
||||
|
||||
@staticmethod
|
||||
def create_driver(settings: BaseAppSettings) -> Any:
|
||||
"""Create Neo4j driver"""
|
||||
return GraphDatabase.driver(
|
||||
settings.neo4j_uri, auth=(settings.neo4j_user, settings.neo4j_password)
|
||||
)
|
||||
|
||||
|
||||
class RedisClientFactory: # pylint: disable=too-few-public-methods
|
||||
"""Factory for creating Redis clients"""
|
||||
|
||||
@staticmethod
|
||||
async def create_client(settings: BaseAppSettings) -> "redis.Redis[str]":
|
||||
"""Create Redis client"""
|
||||
return redis.from_url(
|
||||
settings.redis_url, encoding="utf-8", decode_responses=True
|
||||
)
|
||||
|
||||
|
||||
class EventBusFactory:
|
||||
"""Factory for creating event bus clients"""
|
||||
|
||||
@staticmethod
|
||||
def create_kafka_producer(settings: BaseAppSettings) -> AIOKafkaProducer:
|
||||
"""Create Kafka producer"""
|
||||
return AIOKafkaProducer(
|
||||
bootstrap_servers=settings.kafka_bootstrap_servers,
|
||||
value_serializer=lambda v: v.encode("utf-8") if isinstance(v, str) else v,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_kafka_consumer(
|
||||
settings: BaseAppSettings, topics: list[str]
|
||||
) -> AIOKafkaConsumer:
|
||||
"""Create Kafka consumer"""
|
||||
return AIOKafkaConsumer(
|
||||
*topics,
|
||||
bootstrap_servers=settings.kafka_bootstrap_servers,
|
||||
value_deserializer=lambda m: m.decode("utf-8") if m else None,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_sqs_client(settings: BaseAppSettings) -> Any:
|
||||
"""Create SQS client"""
|
||||
return boto3.client("sqs", region_name=settings.aws_region)
|
||||
|
||||
@staticmethod
|
||||
def create_sns_client(settings: BaseAppSettings) -> Any:
|
||||
"""Create SNS client"""
|
||||
return boto3.client("sns", region_name=settings.aws_region)
|
||||
113
libs/config/settings.py
Normal file
113
libs/config/settings.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Base settings class for all services."""
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class BaseAppSettings(BaseSettings):
|
||||
"""Base settings class for all services"""
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env", env_file_encoding="utf-8", case_sensitive=True, extra="ignore"
|
||||
)
|
||||
|
||||
# Service identification
|
||||
service_name: str = Field(default="default-service", description="Service name")
|
||||
service_version: str = Field(default="1.0.0", description="Service version")
|
||||
|
||||
# Network and security
|
||||
host: str = Field(default="0.0.0.0", description="Service host")
|
||||
port: int = Field(default=8000, description="Service port")
|
||||
internal_cidrs: list[str] = Field(
|
||||
default=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
|
||||
description="Internal network CIDRs",
|
||||
)
|
||||
|
||||
# Development settings
|
||||
dev_mode: bool = Field(
|
||||
default=False,
|
||||
description="Enable development mode (disables auth)",
|
||||
validation_alias="DEV_MODE",
|
||||
)
|
||||
disable_auth: bool = Field(
|
||||
default=False,
|
||||
description="Disable authentication middleware",
|
||||
validation_alias="DISABLE_AUTH",
|
||||
)
|
||||
|
||||
# Vault configuration
|
||||
vault_addr: str = Field(
|
||||
default="http://vault:8200", description="Vault server address"
|
||||
)
|
||||
vault_role_id: str | None = Field(default=None, description="Vault AppRole role ID")
|
||||
vault_secret_id: str | None = Field(
|
||||
default=None, description="Vault AppRole secret ID"
|
||||
)
|
||||
vault_token: str | None = Field(default=None, description="Vault token (dev only)")
|
||||
vault_mount_point: str = Field(
|
||||
default="transit", description="Vault transit mount point"
|
||||
)
|
||||
|
||||
# Database URLs
|
||||
postgres_url: str = Field(
|
||||
default="postgresql://user:pass@postgres:5432/taxagent",
|
||||
description="PostgreSQL connection URL",
|
||||
)
|
||||
neo4j_uri: str = Field(
|
||||
default="bolt://neo4j:7687", description="Neo4j connection URI"
|
||||
)
|
||||
neo4j_user: str = Field(default="neo4j", description="Neo4j username")
|
||||
neo4j_password: str = Field(default="password", description="Neo4j password")
|
||||
redis_url: str = Field(
|
||||
default="redis://redis:6379", description="Redis connection URL"
|
||||
)
|
||||
|
||||
# Object storage
|
||||
minio_endpoint: str = Field(default="minio:9000", description="MinIO endpoint")
|
||||
minio_access_key: str = Field(default="minioadmin", description="MinIO access key")
|
||||
minio_secret_key: str = Field(default="minioadmin", description="MinIO secret key")
|
||||
minio_secure: bool = Field(default=False, description="Use HTTPS for MinIO")
|
||||
|
||||
# Vector database
|
||||
qdrant_url: str = Field(
|
||||
default="http://qdrant:6333", description="Qdrant server URL"
|
||||
)
|
||||
qdrant_api_key: str | None = Field(default=None, description="Qdrant API key")
|
||||
|
||||
# Event bus configuration
|
||||
event_bus_type: str = Field(
|
||||
default="nats", description="Event bus type: nats, kafka, sqs, or memory"
|
||||
)
|
||||
|
||||
# NATS configuration
|
||||
nats_servers: str = Field(
|
||||
default="nats://localhost:4222",
|
||||
description="NATS server URLs (comma-separated)",
|
||||
)
|
||||
nats_stream_name: str = Field(
|
||||
default="TAX_AGENT_EVENTS", description="NATS JetStream stream name"
|
||||
)
|
||||
nats_consumer_group: str = Field(
|
||||
default="tax-agent", description="NATS consumer group name"
|
||||
)
|
||||
|
||||
# Kafka configuration (legacy)
|
||||
kafka_bootstrap_servers: str = Field(
|
||||
default="localhost:9092", description="Kafka bootstrap servers"
|
||||
)
|
||||
|
||||
# AWS configuration
|
||||
aws_region: str = Field(default="us-east-1", description="AWS region for SQS/SNS")
|
||||
|
||||
# Observability
|
||||
otel_service_name: str | None = Field(
|
||||
default=None, description="OpenTelemetry service name"
|
||||
)
|
||||
otel_exporter_endpoint: str | None = Field(
|
||||
default=None, description="OTEL exporter endpoint"
|
||||
)
|
||||
log_level: str = Field(default="INFO", description="Log level")
|
||||
|
||||
# Performance
|
||||
max_workers: int = Field(default=4, description="Maximum worker threads")
|
||||
request_timeout: int = Field(default=30, description="Request timeout in seconds")
|
||||
108
libs/config/utils.py
Normal file
108
libs/config/utils.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Configuration utility functions and global settings management."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import hvac
|
||||
import redis.asyncio as redis
|
||||
from minio import Minio
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
from libs.events.base import EventBus
|
||||
|
||||
from .factories import (
|
||||
MinIOClientFactory,
|
||||
Neo4jDriverFactory,
|
||||
QdrantClientFactory,
|
||||
RedisClientFactory,
|
||||
VaultClientFactory,
|
||||
)
|
||||
from .settings import BaseAppSettings
|
||||
|
||||
# Global settings instance
|
||||
_settings: BaseAppSettings | None = None
|
||||
|
||||
|
||||
def get_settings() -> BaseAppSettings:
|
||||
"""Get global settings instance"""
|
||||
global _settings # pylint: disable=global-variable-not-assigned
|
||||
if _settings is None:
|
||||
raise RuntimeError("Settings not initialized. Call init_settings() first.")
|
||||
return _settings
|
||||
|
||||
|
||||
def init_settings(
|
||||
settings_class: type[BaseAppSettings] = BaseAppSettings, **kwargs: Any
|
||||
) -> BaseAppSettings:
|
||||
"""Initialize settings with custom class"""
|
||||
global _settings # pylint: disable=global-statement
|
||||
_settings = settings_class(**kwargs)
|
||||
return _settings
|
||||
|
||||
|
||||
# Convenience functions for backward compatibility
|
||||
def create_vault_client(settings: BaseAppSettings) -> hvac.Client:
|
||||
"""Create Vault client"""
|
||||
return VaultClientFactory.create_client(settings)
|
||||
|
||||
|
||||
def create_minio_client(settings: BaseAppSettings) -> Minio:
|
||||
"""Create MinIO client"""
|
||||
return MinIOClientFactory.create_client(settings)
|
||||
|
||||
|
||||
def create_qdrant_client(settings: BaseAppSettings) -> QdrantClient:
|
||||
"""Create Qdrant client"""
|
||||
return QdrantClientFactory.create_client(settings)
|
||||
|
||||
|
||||
def create_neo4j_client(settings: BaseAppSettings) -> Any:
|
||||
"""Create Neo4j driver"""
|
||||
return Neo4jDriverFactory.create_driver(settings)
|
||||
|
||||
|
||||
async def create_redis_client(settings: BaseAppSettings) -> "redis.Redis[str]":
|
||||
"""Create Redis client"""
|
||||
return await RedisClientFactory.create_client(settings)
|
||||
|
||||
|
||||
def create_event_bus(settings: BaseAppSettings) -> EventBus:
|
||||
"""Create event bus"""
|
||||
if settings.event_bus_type.lower() == "kafka":
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from ..events import KafkaEventBus
|
||||
|
||||
return KafkaEventBus(settings.kafka_bootstrap_servers)
|
||||
if settings.event_bus_type.lower() == "sqs":
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from ..events import SQSEventBus
|
||||
|
||||
return SQSEventBus(settings.aws_region)
|
||||
if settings.event_bus_type.lower() == "memory":
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from ..events import MemoryEventBus
|
||||
|
||||
return MemoryEventBus()
|
||||
|
||||
# Default to memory bus for unknown types
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from ..events import MemoryEventBus
|
||||
|
||||
return MemoryEventBus()
|
||||
|
||||
|
||||
def get_default_settings(**overrides: Any) -> BaseAppSettings:
|
||||
"""Get default settings with optional overrides"""
|
||||
defaults = {
|
||||
"service_name": "default-service",
|
||||
"vault_addr": "http://vault:8200",
|
||||
"postgres_url": "postgresql://user:pass@postgres:5432/taxagent",
|
||||
"neo4j_uri": "bolt://neo4j:7687",
|
||||
"neo4j_password": "password",
|
||||
"redis_url": "redis://redis:6379",
|
||||
"minio_endpoint": "minio:9000",
|
||||
"minio_access_key": "minioadmin",
|
||||
"minio_secret_key": "minioadmin",
|
||||
"qdrant_url": "http://qdrant:6333",
|
||||
}
|
||||
defaults.update(overrides)
|
||||
return BaseAppSettings(**defaults) # type: ignore
|
||||
Reference in New Issue
Block a user