Squashed 'core/' content from commit 92ec910

git-subtree-dir: core
git-subtree-split: 92ec910a132e379a3a6e442a75bcb07cac0f0010
This commit is contained in:
Du Wenbo
2026-04-04 18:17:10 +08:00
commit 026c837b91
227 changed files with 39179 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
from pydantic_settings import BaseSettings
from functools import lru_cache
import os
import yaml
class Settings(BaseSettings):
APP_NAME: str = "TianpuEMS"
DEBUG: bool = True
API_V1_PREFIX: str = "/api/v1"
# Customer configuration
CUSTOMER: str = "tianpu" # tianpu, zpark, etc.
CUSTOMER_DISPLAY_NAME: str = "" # Loaded from customer config
# Database: set DATABASE_URL in .env to override.
# Default: SQLite for local dev. Docker sets PostgreSQL via env var.
# Examples:
# SQLite: sqlite+aiosqlite:///./tianpu_ems.db
# PostgreSQL: postgresql+asyncpg://tianpu:tianpu2026@localhost:5432/tianpu_ems
DATABASE_URL: str = "sqlite+aiosqlite:///./tianpu_ems.db"
REDIS_URL: str = "redis://localhost:6379/0"
SECRET_KEY: str = "tianpu-ems-secret-key-change-in-production-2026"
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 480
CELERY_ENABLED: bool = False # Set True when Celery worker is running
USE_SIMULATOR: bool = True # True=simulator mode, False=real IoT collectors
# Infrastructure flags
TIMESCALE_ENABLED: bool = False
REDIS_ENABLED: bool = True
INGESTION_QUEUE_ENABLED: bool = False
AGGREGATION_ENABLED: bool = True
# SMTP Email settings
SMTP_HOST: str = ""
SMTP_PORT: int = 587
SMTP_USER: str = ""
SMTP_PASSWORD: str = ""
SMTP_FROM: str = "noreply@tianpu-ems.com"
SMTP_ENABLED: bool = False
# Platform URL for links in emails
PLATFORM_URL: str = "http://localhost:3000"
@property
def DATABASE_URL_SYNC(self) -> str:
"""Derive synchronous URL from async DATABASE_URL for Alembic."""
url = self.DATABASE_URL
return url.replace("+aiosqlite", "").replace("+asyncpg", "+psycopg2")
@property
def is_sqlite(self) -> bool:
return "sqlite" in self.DATABASE_URL
@property
def customer_config_path(self) -> str:
"""Search for customer config in multiple locations."""
backend_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
# Standalone: project_root/customers/{CUSTOMER}/
path1 = os.path.join(backend_dir, "..", "customers", self.CUSTOMER)
if os.path.isdir(path1):
return os.path.abspath(path1)
# Subtree: customer_project_root/customers/{CUSTOMER}/ (core is 2 levels up)
path2 = os.path.join(backend_dir, "..", "..", "customers", self.CUSTOMER)
if os.path.isdir(path2):
return os.path.abspath(path2)
return os.path.abspath(path1) # Default fallback
def load_customer_config(self) -> dict:
"""Load customer-specific config from customers/{CUSTOMER}/config.yaml"""
config_file = os.path.join(self.customer_config_path, "config.yaml")
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
return yaml.safe_load(f) or {}
return {}
class Config:
env_file = ".env"
extra = "ignore"
@lru_cache
def get_settings() -> Settings:
return Settings()