diff --git a/conf/config_models.py b/conf/config_models.py index ab26162..c8298ed 100644 --- a/conf/config_models.py +++ b/conf/config_models.py @@ -4,6 +4,7 @@ import os import dotenv +import yaml @dataclass @@ -40,54 +41,118 @@ class UsageCollectionMongoConfig: mongo_uri: str = "mongodb://admin:admin@localhost:27017" +@dataclass +class SmartyConfig: + base_url: str = "https://smarty.smartbank.uz" + + @dataclass class AppConfig: logfire: LogfireConfig sentry: SentryConfig otel: OTELConfig + smarty: SmartyConfig environment: str = "development" mongo: UsageCollectionMongoConfig | None = None def New() -> AppConfig: dotenv.load_dotenv(".env") - # with open(".env", "r") as f: - # print("ENV FILE: ", f.read()) - - # for key, value in os.environ.items(): - # print(f"{key}: {value}") - logfire_config = LogfireConfig(token=os.getenv("LOGFIRE_TOKEN", "")) - sentry_config = SentryConfig(dsn=os.getenv("SENTRY_DSN", "")) + environment = os.getenv("ENVIRONMENT", "development").lower() + + # Load per-environment defaults from config.yaml + env_cfg: dict = {} + try: + with open("config.yaml", "r") as f: + all_configs = yaml.safe_load(f) or {} + env_cfg = all_configs.get(environment, {}) + except FileNotFoundError: + pass + + logfire_cfg = env_cfg.get("logfire", {}) + sentry_cfg = env_cfg.get("sentry", {}) + otel_cfg = env_cfg.get("otel", {}) + mongo_cfg = env_cfg.get("mongo", {}) + smarty_cfg = env_cfg.get("smarty", {}) + # Resolve console_export: env var overrides yaml, yaml overrides False default + console_export_env = os.getenv("CONSOLE_EXPORT") + if console_export_env is not None: + console_export = console_export_env.lower() in ("true", "1", "yes") + else: + console_export = bool(otel_cfg.get("console_export", False)) + + logfire_config = LogfireConfig( + token=os.getenv("LOGFIRE_TOKEN", logfire_cfg.get("token", "")) + ) + sentry_config = SentryConfig(dsn=os.getenv("SENTRY_DSN", sentry_cfg.get("dsn", ""))) otel_config = OTELConfig( - service_name=os.getenv("SERVICE_NAME", "ui_server"), + service_name=os.getenv( + "SERVICE_NAME", otel_cfg.get("service_name", "ui_server") + ), resource_attributes=os.getenv( "RESOURCE_ATTRIBUTES", - "deployment.environment=PRODUCTION,service.namespace=ui-server", + otel_cfg.get( + "resource_attributes", + "deployment.environment=production,service.namespace=ui-server", + ), + ), + bsp_schedule_delay=int( + os.getenv("BSP_SCHEDULE_DELAY", otel_cfg.get("bsp_schedule_delay", 5000)) + ), + bsp_max_queue_size=int( + os.getenv("BSP_MAX_QUEUE_SIZE", otel_cfg.get("bsp_max_queue_size", 2048)) + ), + bsp_max_export_batch_size=int( + os.getenv( + "BSP_MAX_EXPORT_BATCH_SIZE", + otel_cfg.get("bsp_max_export_batch_size", 512), + ) + ), + bsp_export_timeout=int( + os.getenv("BSP_EXPORT_TIMEOUT", otel_cfg.get("bsp_export_timeout", 30000)) + ), + traces_sampler=os.getenv( + "TRACES_SAMPLER", otel_cfg.get("traces_sampler", "parentbased_traceidratio") + ), + traces_sampler_arg=float( + os.getenv("TRACES_SAMPLER_ARG", otel_cfg.get("traces_sampler_arg", 1.0)) ), - bsp_schedule_delay=int(os.getenv("BSP_SCHEDULE_DELAY", 5000)), - bsp_max_queue_size=int(os.getenv("BSP_MAX_QUEUE_SIZE", 2048)), - bsp_max_export_batch_size=int(os.getenv("BSP_MAX_EXPORT_BATCH_SIZE", 512)), - bsp_export_timeout=int(os.getenv("BSP_EXPORT_TIMEOUT", 30000)), - traces_sampler=os.getenv("TRACES_SAMPLER", "parentbased_traceidratio"), - traces_sampler_arg=float(os.getenv("TRACES_SAMPLER_ARG", 1.0)), exporter_otlp_endpoint=os.getenv( - "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318/v1/traces" + "OTEL_EXPORTER_OTLP_ENDPOINT", + otel_cfg.get("exporter_otlp_endpoint", "http://localhost:4318/v1/traces"), ), - exporter_otlp_headers=os.getenv("EXPORTER_OTLP_HEADERS", None), - console_export=bool(os.getenv("CONSOLE_EXPORT", False)), + exporter_otlp_headers=os.getenv( + "EXPORTER_OTLP_HEADERS", otel_cfg.get("exporter_otlp_headers") + ), + console_export=console_export, ) return AppConfig( - environment=os.getenv("ENVIRONMENT", "development"), + environment=environment, logfire=logfire_config, sentry=sentry_config, otel=otel_config, mongo=UsageCollectionMongoConfig( - database_name=os.getenv("MONGO_DATABASE_NAME", "usage"), - collection_name=os.getenv("MONGO_COLLECTION_NAME", "ui_server"), - mongo_uri=os.getenv("MONGO_URI", "mongodb://admin:admin@localhost:27017"), + database_name=os.getenv( + "MONGO_DATABASE_NAME", mongo_cfg.get("database_name", "usage") + ), + collection_name=os.getenv( + "MONGO_COLLECTION_NAME", mongo_cfg.get("collection_name", "ui_server") + ), + mongo_uri=os.getenv( + "MONGO_URI", + mongo_cfg.get("mongo_uri", "mongodb://admin:admin@localhost:27017"), + ), + ), + smarty=SmartyConfig( + base_url=os.getenv( + "SMARTY_BASE_URL", + smarty_cfg.get("base_url", "https://smarty.smartbank.uz"), + ), ), ) config = New() + +print("Config: ", config) diff --git a/conf/logger_conf.py b/conf/logger_conf.py index 6b3e9bc..a4f7a5e 100644 --- a/conf/logger_conf.py +++ b/conf/logger_conf.py @@ -26,10 +26,21 @@ def setup_logging(logfile: str) -> structlog.stdlib.BoundLogger: # --- 1. Create handlers --- console_handler = logging.StreamHandler(sys.stdout) log_level = logging.DEBUG - if os.getenv("ENVIRONMENT", "development").lower() == "development": + if os.getenv("LOG_LEVEL", "DEBUG").upper() == "DEBUG": log_level = logging.DEBUG - else: + elif os.getenv("LOG_LEVEL", "DEBUG").upper() == "INFO": log_level = logging.INFO + elif os.getenv("LOG_LEVEL", "DEBUG").upper() == "WARNING": + log_level = logging.WARNING + elif os.getenv("LOG_LEVEL", "DEBUG").upper() == "ERROR": + log_level = logging.ERROR + elif os.getenv("LOG_LEVEL", "DEBUG").upper() == "CRITICAL": + log_level = logging.CRITICAL + elif os.getenv("LOG_LEVEL", "DEBUG").upper() == "FATAL": + log_level = logging.FATAL + else: + log_level = logging.DEBUG + console_handler.setLevel(log_level) console_handler.setFormatter(logging.Formatter(fmt="%(message)s")) diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..2b2f3e5 --- /dev/null +++ b/config.yaml @@ -0,0 +1,47 @@ +development: + logfire: + token: "" + sentry: + dsn: "" + otel: + service_name: ui_server + resource_attributes: "deployment.environment=development,service.namespace=ui-server" + bsp_schedule_delay: 5000 + bsp_max_queue_size: 2048 + bsp_max_export_batch_size: 512 + bsp_export_timeout: 30000 + traces_sampler: parentbased_traceidratio + traces_sampler_arg: 1.0 + exporter_otlp_endpoint: "http://localhost:4318/v1/traces" + exporter_otlp_headers: null + console_export: false + mongo: + database_name: usage + collection_name: ui_server + mongo_uri: "mongodb://admin:admin@localhost:27017" + smarty: + base_url: "https://smarty-test.smartbank.uz" + +production: + logfire: + token: "" + sentry: + dsn: "" + otel: + service_name: ui_server + resource_attributes: "deployment.environment=production,service.namespace=ui-server" + bsp_schedule_delay: 5000 + bsp_max_queue_size: 2048 + bsp_max_export_batch_size: 512 + bsp_export_timeout: 30000 + traces_sampler: parentbased_traceidratio + traces_sampler_arg: 1.0 + exporter_otlp_endpoint: "http://tempo:4318/v1/traces" + exporter_otlp_headers: null + console_export: false + mongo: + database_name: usage + collection_name: ui_server + mongo_uri: "mongodb://admin:admin@mongo:27017" + smarty: + base_url: "https://smarty.smartbank.uz" diff --git a/functions_to_format/functions/products.py b/functions_to_format/functions/products.py index 7b69670..b67dda7 100644 --- a/functions_to_format/functions/products.py +++ b/functions_to_format/functions/products.py @@ -24,6 +24,7 @@ import structlog from models.context import Context from .base_strategy import FunctionStrategy +from conf import config # Import smarty_ui components from smarty_ui import ( @@ -393,7 +394,7 @@ def make_product_state( typed=dv.DivActionSubmit( container_id=cart_container, request=dv.DivActionSubmitRequest( - url=f"https://smarty.smartbank.uz/chat/v3/tools/call?function_name=add_product_to_cart&chat_id={chat_id}&arguments={json.dumps({'offer_id': p.offer_id, 'quantity': 1})}", + url=f"{config.smarty.base_url}/chat/v3/tools/call?function_name=add_product_to_cart&chat_id={chat_id}&arguments={json.dumps({'offer_id': p.offer_id, 'quantity': 1})}", method=dv.RequestMethod.POST, headers=[dv.RequestHeader(name="api-key", value=api_key)], ), diff --git a/src/server.py b/src/server.py index a3bb4b9..256fa19 100644 --- a/src/server.py +++ b/src/server.py @@ -44,7 +44,12 @@ version = "0" logger.info("Starting server", env=os.getenv("ENVIRONMENT"), version=version) -app = FastAPI() +app = FastAPI( + title="UI Server", + description="UI Server to generate UI json representations using Yandex Divkit", + version=version, + openapi_url="/ui_server/openapi.json", +) # Setup telemetry setup_telemetry(app, service_name="ui_server", version=version) @@ -72,7 +77,7 @@ logger.info(f"Deleted file: {file}") # Serve static files -app.mount("/ui_server/static", StaticFiles(directory="static"), name="static") +app.mount("/static", StaticFiles(directory="static"), name="static") # https://www.youtbe.com/watch?v=NTP4XdTjRK0 diff --git a/telemetry/setup.py b/telemetry/setup.py index 6555649..bd588c0 100644 --- a/telemetry/setup.py +++ b/telemetry/setup.py @@ -82,8 +82,9 @@ def setup_telemetry( _prometheus_reader = PrometheusMetricReader() metric_readers.append(_prometheus_reader) - if environment == "development" or config.otel.console_export: + if config.otel.console_export: # Add console exporter for development + print("Adding console exporter for development") console_metric_exporter = ConsoleMetricExporter() metric_readers.append( PeriodicExportingMetricReader( diff --git a/uv.lock b/uv.lock index ec86352..cd8f725 100644 --- a/uv.lock +++ b/uv.lock @@ -1744,7 +1744,7 @@ wheels = [ [[package]] name = "smarty-ui" -version = "0.1.5" +version = "0.1.9" source = { directory = "smarty_ui" } dependencies = [ { name = "pydantic" },