logs: migrate to molecular CoT JSON protocol, fix regressions
- Replace BeliefFormatter with CotJsonFormatter (single-line JSON with ts, level, trace_id, src, marker, intent, payload, error, span_id) - Migrate belief_scope to use cot_log() for structured JSON output - Update monkey-patched reason/reflect/explore to pass extra= dict - Strip 200+ inline [REASON]/[REFLECT]/[EXPLORE] markers from 50+ files - Remove belief_scope from hot-path utilities (_parse_datetime, _json_load_if_needed) to eliminate startup log noise - Register TraceContextMiddleware in app.py (was implemented but unused) - Seed trace_id in startup_event/shutdown_event for background tasks - Fix broken relative imports in translate routes (3 dots → 4 dots) - Migrate 43 translate_routes log calls to logger.reason/explore - Fix SyntaxError (positional arg after extra=) in _repo_operations_routes - Fix IndentationError in rbac_permission_catalog except-block - Add smoke test tests/test_smoke_app.py (catches import errors before run)
This commit is contained in:
@@ -310,9 +310,9 @@ async def list_permissions(
|
||||
db=db, declared_permissions=declared_permissions
|
||||
)
|
||||
if inserted_count > 0:
|
||||
logger.info(
|
||||
"[api.admin.list_permissions][REASON] Synchronized %s missing RBAC permissions into auth catalog",
|
||||
inserted_count,
|
||||
logger.reason(
|
||||
f"Synchronized {inserted_count} missing RBAC permissions into auth catalog",
|
||||
extra={"src": "api.admin.list_permissions"},
|
||||
)
|
||||
|
||||
repo = AuthRepository(db)
|
||||
|
||||
@@ -141,7 +141,8 @@ async def get_dashboards(
|
||||
)
|
||||
except Exception as profile_error:
|
||||
logger.explore(
|
||||
f"[EXPLORE] Profile preference unavailable; continuing without profile-default filter: {profile_error}"
|
||||
f"Profile preference unavailable; continuing without profile-default filter: {profile_error}",
|
||||
extra={"src": "get_dashboards"},
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -183,9 +184,9 @@ async def get_dashboards(
|
||||
total = page_payload["total"]
|
||||
total_pages = page_payload["total_pages"]
|
||||
except Exception as page_error:
|
||||
logger.warning(
|
||||
"[get_dashboards][REASON] Page-based fetch failed; using compatibility fallback: %s",
|
||||
page_error,
|
||||
logger.reason(
|
||||
f"Page-based fetch failed; using compatibility fallback: {page_error}",
|
||||
extra={"src": "get_dashboards"},
|
||||
)
|
||||
if can_apply_slug_filter:
|
||||
dashboards = await resource_service.get_dashboards_with_status(
|
||||
@@ -241,9 +242,8 @@ async def get_dashboards(
|
||||
if not actor_aliases:
|
||||
actor_aliases = [bound_username]
|
||||
logger.reason(
|
||||
"[REASON] Applying profile actor filter "
|
||||
f"(env={env_id}, bound_username={bound_username}, actor_aliases={actor_aliases!r}, "
|
||||
f"dashboards_before={len(dashboards)})"
|
||||
"Applying profile actor filter",
|
||||
extra={"src": "get_dashboards", "payload": {"env": env_id, "bound_username": bound_username, "actor_aliases": actor_aliases, "dashboards_before": len(dashboards)}},
|
||||
)
|
||||
filtered_dashboards: List[Dict[str, Any]] = []
|
||||
max_actor_samples = 15
|
||||
@@ -259,19 +259,15 @@ async def get_dashboards(
|
||||
)
|
||||
if index < max_actor_samples:
|
||||
logger.reflect(
|
||||
"[REFLECT] Profile actor filter sample "
|
||||
f"(env={env_id}, dashboard_id={dashboard.get('id')}, "
|
||||
f"bound_username={bound_username!r}, actor_aliases={actor_aliases!r}, "
|
||||
f"owners={owners_value!r}, created_by={created_by_value!r}, "
|
||||
f"modified_by={modified_by_value!r}, matches={matches_actor})"
|
||||
"Profile actor filter sample",
|
||||
extra={"src": "get_dashboards", "payload": {"env": env_id, "dashboard_id": dashboard.get('id'), "bound_username": bound_username, "actor_aliases": actor_aliases, "owners": owners_value, "created_by": created_by_value, "modified_by": modified_by_value, "matches": matches_actor}},
|
||||
)
|
||||
if matches_actor:
|
||||
filtered_dashboards.append(dashboard)
|
||||
|
||||
logger.reflect(
|
||||
"[REFLECT] Profile actor filter summary "
|
||||
f"(env={env_id}, bound_username={bound_username!r}, "
|
||||
f"dashboards_before={len(dashboards)}, dashboards_after={len(filtered_dashboards)})"
|
||||
"Profile actor filter summary",
|
||||
extra={"src": "get_dashboards", "payload": {"env": env_id, "bound_username": bound_username, "dashboards_before": len(dashboards), "dashboards_after": len(filtered_dashboards)}},
|
||||
)
|
||||
dashboards = filtered_dashboards
|
||||
|
||||
|
||||
@@ -182,14 +182,13 @@ def _resolve_profile_actor_aliases(env: Any, bound_username: str) -> List[str]:
|
||||
if display_alias and display_alias not in aliases:
|
||||
aliases.append(display_alias)
|
||||
logger.reflect(
|
||||
"[REFLECT] Resolved profile actor aliases "
|
||||
f"(env={getattr(env, 'id', None)}, bound_username={normalized_bound!r}, "
|
||||
f"lookup_items={len(lookup_items)}, aliases={aliases!r})"
|
||||
"Resolved profile actor aliases",
|
||||
extra={"src": "_resolve_profile_actor_aliases", "payload": {"env": getattr(env, 'id', None), "bound_username": normalized_bound, "lookup_items": len(lookup_items), "aliases": aliases}},
|
||||
)
|
||||
except Exception as alias_error:
|
||||
logger.explore(
|
||||
"[EXPLORE] Failed to resolve profile actor aliases via Superset users lookup "
|
||||
f"(env={getattr(env, 'id', None)}, bound_username={normalized_bound!r}): {alias_error}"
|
||||
"Failed to resolve profile actor aliases via Superset users lookup",
|
||||
extra={"src": "_resolve_profile_actor_aliases", "payload": {"env": getattr(env, 'id', None), "bound_username": normalized_bound}, "error": str(alias_error)},
|
||||
)
|
||||
return aliases
|
||||
|
||||
|
||||
@@ -61,8 +61,9 @@ def _resolve_repository_status(dashboard_id: int) -> dict:
|
||||
git_service = get_git_service()
|
||||
repo_path = git_service._get_repo_path(dashboard_id)
|
||||
if not os.path.exists(repo_path):
|
||||
logger.debug(
|
||||
f"[get_repository_status][REASON] Repository is not initialized for dashboard {dashboard_id}"
|
||||
logger.reason(
|
||||
f"Repository is not initialized for dashboard {dashboard_id}",
|
||||
extra={"src": "get_repository_status"},
|
||||
)
|
||||
return _build_no_repo_status_payload()
|
||||
|
||||
@@ -70,8 +71,9 @@ def _resolve_repository_status(dashboard_id: int) -> dict:
|
||||
return git_service.get_status(dashboard_id)
|
||||
except HTTPException as e:
|
||||
if e.status_code == 404:
|
||||
logger.debug(
|
||||
f"[get_repository_status][REASON] Repository is not initialized for dashboard {dashboard_id}"
|
||||
logger.reason(
|
||||
f"Repository is not initialized for dashboard {dashboard_id}",
|
||||
extra={"src": "get_repository_status"},
|
||||
)
|
||||
return _build_no_repo_status_payload()
|
||||
raise
|
||||
@@ -287,10 +289,9 @@ def _resolve_current_user_git_identity(
|
||||
.first()
|
||||
)
|
||||
except Exception as resolve_error:
|
||||
logger.warning(
|
||||
"[_resolve_current_user_git_identity][REASON] Failed to load profile preference for user %s: %s",
|
||||
user_id,
|
||||
resolve_error,
|
||||
logger.explore(
|
||||
"Failed to load profile preference for resolving git identity",
|
||||
extra={"src": "_resolve_current_user_git_identity", "payload": {"user_id": user_id}, "error": str(resolve_error)},
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@@ -128,23 +128,17 @@ async def pull_changes(
|
||||
config_url = config_row.url
|
||||
config_provider = config_row.provider
|
||||
except Exception as diagnostics_error:
|
||||
logger.warning(
|
||||
"[pull_changes][REASON] Failed to load repository binding diagnostics for dashboard %s: %s",
|
||||
dashboard_id,
|
||||
diagnostics_error,
|
||||
logger.explore(
|
||||
"Failed to load repository binding diagnostics",
|
||||
extra={"src": "pull_changes", "payload": {"dashboard_id": dashboard_id}, "error": str(diagnostics_error)},
|
||||
)
|
||||
logger.info(
|
||||
"[pull_changes][REASON] Route diagnostics dashboard_ref=%s env_id=%s resolved_dashboard_id=%s "
|
||||
"binding_exists=%s binding_local_path=%s binding_remote_url=%s binding_config_id=%s config_provider=%s config_url=%s",
|
||||
dashboard_ref,
|
||||
env_id,
|
||||
dashboard_id,
|
||||
bool(db_repo),
|
||||
(db_repo.local_path if db_repo else None),
|
||||
(db_repo.remote_url if db_repo else None),
|
||||
(db_repo.config_id if db_repo else None),
|
||||
config_provider,
|
||||
config_url,
|
||||
logger.reason(
|
||||
f"Route diagnostics dashboard_ref={dashboard_ref} env_id={env_id} resolved_dashboard_id={dashboard_id} "
|
||||
f"binding_exists={bool(db_repo)} binding_local_path={(db_repo.local_path if db_repo else None)} "
|
||||
f"binding_remote_url={(db_repo.remote_url if db_repo else None)} "
|
||||
f"binding_config_id={(db_repo.config_id if db_repo else None)} "
|
||||
f"config_provider={config_provider} config_url={config_url}",
|
||||
extra={"src": "pull_changes"},
|
||||
)
|
||||
_apply_git_identity_from_profile(dashboard_id, db, current_user)
|
||||
_gs.pull_changes(dashboard_id)
|
||||
@@ -190,10 +184,9 @@ async def get_repository_status_batch(
|
||||
with belief_scope("get_repository_status_batch"):
|
||||
dashboard_ids = list(dict.fromkeys(request.dashboard_ids))
|
||||
if len(dashboard_ids) > MAX_REPOSITORY_STATUS_BATCH:
|
||||
logger.warning(
|
||||
"[get_repository_status_batch][REASON] Batch size %s exceeds limit %s. Truncating request.",
|
||||
len(dashboard_ids),
|
||||
MAX_REPOSITORY_STATUS_BATCH,
|
||||
logger.reason(
|
||||
f"Batch size {len(dashboard_ids)} exceeds limit {MAX_REPOSITORY_STATUS_BATCH}. Truncating request.",
|
||||
extra={"src": "get_repository_status_batch"},
|
||||
)
|
||||
dashboard_ids = dashboard_ids[:MAX_REPOSITORY_STATUS_BATCH]
|
||||
|
||||
|
||||
@@ -61,8 +61,9 @@ async def init_repository(
|
||||
raise HTTPException(status_code=404, detail="Git configuration not found")
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
f"[init_repository][REASON] Initializing repo for dashboard {dashboard_id}"
|
||||
logger.reason(
|
||||
f"Initializing repo for dashboard {dashboard_id}",
|
||||
extra={"src": "init_repository"},
|
||||
)
|
||||
_gs.init_repo(
|
||||
dashboard_id,
|
||||
|
||||
@@ -52,8 +52,9 @@ async def get_providers(
|
||||
"""
|
||||
Get all LLM provider configurations.
|
||||
"""
|
||||
logger.info(
|
||||
f"[llm_routes][get_providers][REASON] Fetching providers for user: {current_user.username}"
|
||||
logger.reason(
|
||||
f"Fetching providers for user: {current_user.username}",
|
||||
extra={"src": "llm_routes.get_providers"},
|
||||
)
|
||||
service = LLMProviderService(db)
|
||||
providers = service.get_all_providers()
|
||||
@@ -248,8 +249,9 @@ async def test_connection(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
logger.info(
|
||||
f"[llm_routes][test_connection][REASON] Testing connection for provider_id: {provider_id}"
|
||||
logger.reason(
|
||||
f"Testing connection for provider_id: {provider_id}",
|
||||
extra={"src": "llm_routes.test_connection"},
|
||||
)
|
||||
"""
|
||||
Test connection to an LLM provider.
|
||||
@@ -305,8 +307,9 @@ async def test_provider_config(
|
||||
"""
|
||||
from ...plugins.llm_analysis.service import LLMClient
|
||||
|
||||
logger.info(
|
||||
f"[llm_routes][test_provider_config][REASON] Testing config for {config.name}"
|
||||
logger.reason(
|
||||
f"Testing config for {config.name}",
|
||||
extra={"src": "llm_routes.test_provider_config"},
|
||||
)
|
||||
|
||||
# Check if API key is provided
|
||||
|
||||
@@ -357,8 +357,9 @@ async def trigger_sync_now(
|
||||
credentials_id=env.id, # Use env.id as credentials reference
|
||||
)
|
||||
db.add(db_env)
|
||||
logger.info(
|
||||
f"[trigger_sync_now][REASON] Created environment row for {env.id}"
|
||||
logger.reason(
|
||||
f"Created environment row for {env.id}",
|
||||
extra={"src": "trigger_sync_now"},
|
||||
)
|
||||
else:
|
||||
existing.name = env.name
|
||||
@@ -373,7 +374,7 @@ async def trigger_sync_now(
|
||||
client = SupersetClient(env)
|
||||
service.sync_environment(env.id, client)
|
||||
results["synced"].append(env.id)
|
||||
logger.info(f"[trigger_sync_now][REASON] Synced environment {env.id}")
|
||||
logger.reason(f"Synced environment {env.id}", extra={"src": "trigger_sync_now"})
|
||||
except Exception as e:
|
||||
results["failed"].append({"env_id": env.id, "error": str(e)})
|
||||
logger.error(f"[trigger_sync_now][Error] Failed to sync {env.id}: {e}")
|
||||
|
||||
@@ -69,7 +69,7 @@ async def get_preferences(
|
||||
plugin_loader=Depends(get_plugin_loader),
|
||||
):
|
||||
with belief_scope("profile.get_preferences", f"user_id={current_user.id}"):
|
||||
logger.reason("[REASON] Resolving current user preference")
|
||||
logger.reason("Resolving current user preference")
|
||||
service = _get_profile_service(db, config_manager, plugin_loader)
|
||||
return service.get_my_preference(current_user)
|
||||
# #endregion get_preferences
|
||||
@@ -91,13 +91,13 @@ async def update_preferences(
|
||||
with belief_scope("profile.update_preferences", f"user_id={current_user.id}"):
|
||||
service = _get_profile_service(db, config_manager, plugin_loader)
|
||||
try:
|
||||
logger.reason("[REASON] Attempting preference save")
|
||||
logger.reason("Attempting preference save")
|
||||
return service.update_my_preference(current_user=current_user, payload=payload)
|
||||
except ProfileValidationError as exc:
|
||||
logger.reflect("[REFLECT] Preference validation failed")
|
||||
logger.reflect("Preference validation failed")
|
||||
raise HTTPException(status_code=422, detail=exc.errors) from exc
|
||||
except ProfileAuthorizationError as exc:
|
||||
logger.explore("[EXPLORE] Cross-user mutation guard blocked request")
|
||||
logger.explore("Cross-user mutation guard blocked request")
|
||||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||||
# #endregion update_preferences
|
||||
|
||||
@@ -134,13 +134,13 @@ async def lookup_superset_accounts(
|
||||
sort_order=sort_order,
|
||||
)
|
||||
try:
|
||||
logger.reason("[REASON] Executing Superset account lookup")
|
||||
logger.reason("Executing Superset account lookup")
|
||||
return service.lookup_superset_accounts(
|
||||
current_user=current_user,
|
||||
request=lookup_request,
|
||||
)
|
||||
except EnvironmentNotFoundError as exc:
|
||||
logger.explore("[EXPLORE] Lookup request references unknown environment")
|
||||
logger.explore("Lookup request references unknown environment")
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
# #endregion lookup_superset_accounts
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ async def get_settings(
|
||||
_=Depends(has_permission("admin:settings", "READ")),
|
||||
):
|
||||
with belief_scope("get_settings"):
|
||||
logger.info("[get_settings][REASON] Fetching all settings")
|
||||
logger.reason("Fetching all settings", extra={"src": "get_settings"})
|
||||
config = config_manager.get_config().copy(deep=True)
|
||||
config.settings.llm = normalize_llm_settings(config.settings.llm)
|
||||
# Mask passwords
|
||||
@@ -127,7 +127,7 @@ async def update_global_settings(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("update_global_settings"):
|
||||
logger.info("[update_global_settings][REASON] Updating global settings")
|
||||
logger.reason("Updating global settings", extra={"src": "update_global_settings"})
|
||||
|
||||
config_manager.update_global_settings(settings)
|
||||
return settings
|
||||
@@ -183,7 +183,7 @@ async def get_environments(
|
||||
_=Depends(has_permission("admin:settings", "READ")),
|
||||
):
|
||||
with belief_scope("get_environments"):
|
||||
logger.info("[get_environments][REASON] Fetching environments")
|
||||
logger.reason("Fetching environments", extra={"src": "get_environments"})
|
||||
environments = config_manager.get_environments()
|
||||
return [
|
||||
env.copy(update={"url": _normalize_superset_env_url(env.url)})
|
||||
@@ -205,7 +205,7 @@ async def add_environment(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("add_environment"):
|
||||
logger.info(f"[add_environment][REASON] Adding environment {env.id}")
|
||||
logger.reason(f"Adding environment {env.id}", extra={"src": "add_environment"})
|
||||
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
|
||||
|
||||
# Validate connection before adding (fast path)
|
||||
@@ -237,7 +237,7 @@ async def update_environment(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
with belief_scope("update_environment"):
|
||||
logger.info(f"[update_environment][REASON] Updating environment {id}")
|
||||
logger.reason(f"Updating environment {id}", extra={"src": "update_environment"})
|
||||
|
||||
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
|
||||
|
||||
@@ -278,7 +278,7 @@ async def delete_environment(
|
||||
id: str, config_manager: ConfigManager = Depends(get_config_manager)
|
||||
):
|
||||
with belief_scope("delete_environment"):
|
||||
logger.info(f"[delete_environment][REASON] Deleting environment {id}")
|
||||
logger.reason(f"Deleting environment {id}", extra={"src": "delete_environment"})
|
||||
config_manager.delete_environment(id)
|
||||
return {"message": f"Environment {id} deleted"}
|
||||
|
||||
@@ -295,7 +295,7 @@ async def test_environment_connection(
|
||||
id: str, config_manager: ConfigManager = Depends(get_config_manager)
|
||||
):
|
||||
with belief_scope("test_environment_connection"):
|
||||
logger.info(f"[test_environment_connection][REASON] Testing environment {id}")
|
||||
logger.reason(f"Testing environment {id}", extra={"src": "test_environment_connection"})
|
||||
|
||||
# Find environment
|
||||
env = next((e for e in config_manager.get_environments() if e.id == id), None)
|
||||
@@ -351,8 +351,8 @@ async def update_logging_config(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("update_logging_config"):
|
||||
logger.info(
|
||||
f"[update_logging_config][REASON] Updating logging config: level={config.level}, task_log_level={config.task_log_level}"
|
||||
logger.reason(
|
||||
f"Updating logging config: level={config.level}, task_log_level={config.task_log_level}", extra={"src": "update_logging_config"}
|
||||
)
|
||||
|
||||
# Get current settings and update logging config
|
||||
@@ -475,8 +475,8 @@ async def update_consolidated_settings(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("update_consolidated_settings"):
|
||||
logger.info(
|
||||
"[update_consolidated_settings][REASON] Applying consolidated settings patch"
|
||||
logger.reason(
|
||||
"Applying consolidated settings patch", extra={"src": "update_consolidated_settings"}
|
||||
)
|
||||
|
||||
current_config = config_manager.get_config()
|
||||
|
||||
@@ -36,7 +36,7 @@ async def submit_correction(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Submit a term correction from a run result review."""
|
||||
logger.info(f"[translate_routes][submit_correction] User: {current_user.username}")
|
||||
logger.reason(f"submit_correction — User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
if not payload.dictionary_id:
|
||||
raise HTTPException(status_code=422, detail="dictionary_id is required")
|
||||
try:
|
||||
@@ -68,7 +68,7 @@ async def submit_bulk_corrections(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Submit multiple term corrections atomically."""
|
||||
logger.info(f"[translate_routes][submit_bulk_corrections] User: {current_user.username}")
|
||||
logger.reason(f"submit_bulk_corrections — User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
corr_list = []
|
||||
for c in payload.corrections:
|
||||
|
||||
@@ -67,7 +67,7 @@ async def get_dictionary(
|
||||
logger.reason(f"Dict: {dictionary_id}, User: {current_user.username}")
|
||||
try:
|
||||
d = DictionaryManager.get_dictionary(db, dictionary_id)
|
||||
from ...models.translate import DictionaryEntry
|
||||
from ....models.translate import DictionaryEntry
|
||||
entry_count = db.query(DictionaryEntry.id).filter(DictionaryEntry.dictionary_id == dictionary_id).count()
|
||||
logger.reflect("Dictionary found", {"id": dictionary_id})
|
||||
return _dict_to_response(d, entry_count)
|
||||
@@ -129,7 +129,7 @@ async def update_dictionary(
|
||||
target_dialect=payload.target_dialect,
|
||||
is_active=payload.is_active,
|
||||
)
|
||||
from ...models.translate import DictionaryEntry
|
||||
from ....models.translate import DictionaryEntry
|
||||
entry_count = db.query(DictionaryEntry.id).filter(DictionaryEntry.dictionary_id == dictionary_id).count()
|
||||
logger.reflect("Dictionary updated", {"id": dictionary_id})
|
||||
return _dict_to_response(d, entry_count)
|
||||
|
||||
@@ -57,7 +57,7 @@ def _dict_to_response(d: Any, entry_count: int = 0) -> dict:
|
||||
# @BRIEF Get entry counts for a list of dictionary IDs.
|
||||
def _get_dictionary_entry_counts(db: Session, dict_ids: List[str]) -> Dict[str, int]:
|
||||
from sqlalchemy import func
|
||||
from ...models.translate import DictionaryEntry
|
||||
from ....models.translate import DictionaryEntry
|
||||
counts = (
|
||||
db.query(DictionaryEntry.dictionary_id, func.count(DictionaryEntry.id))
|
||||
.filter(DictionaryEntry.dictionary_id.in_(dict_ids))
|
||||
|
||||
@@ -46,7 +46,7 @@ async def list_jobs(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""List all translation jobs."""
|
||||
logger.info(f"[translate_routes][list_jobs] User: {current_user.username}")
|
||||
logger.reason(f"list_jobs — User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
service = TranslateJobService(db, config_manager, current_user.username)
|
||||
total, jobs = service.list_jobs(
|
||||
@@ -77,7 +77,7 @@ async def get_job(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get a translation job by ID."""
|
||||
logger.info(f"[translate_routes][get_job] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_job — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
service = TranslateJobService(db, config_manager, current_user.username)
|
||||
job = service.get_job(job_id)
|
||||
@@ -102,7 +102,7 @@ async def create_job(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Create a new translation job."""
|
||||
logger.info(f"[translate_routes][create_job] User: {current_user.username}")
|
||||
logger.reason(f"create_job — User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
service = TranslateJobService(db, config_manager, current_user.username)
|
||||
job = service.create_job(payload)
|
||||
@@ -128,7 +128,7 @@ async def update_job(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Update a translation job."""
|
||||
logger.info(f"[translate_routes][update_job] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"update_job — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
service = TranslateJobService(db, config_manager, current_user.username)
|
||||
job = service.update_job(job_id, payload)
|
||||
@@ -152,7 +152,7 @@ async def delete_job(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Delete a translation job."""
|
||||
logger.info(f"[translate_routes][delete_job] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"delete_job — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
service = TranslateJobService(db, config_manager, current_user.username)
|
||||
service.delete_job(job_id)
|
||||
@@ -174,7 +174,7 @@ async def duplicate_job(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Duplicate a translation job."""
|
||||
logger.info(f"[translate_routes][duplicate_job] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"duplicate_job — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
service = TranslateJobService(db, config_manager, current_user.username)
|
||||
new_job = service.duplicate_job(job_id)
|
||||
@@ -212,7 +212,7 @@ async def list_datasources(
|
||||
datasets = service.fetch_available_datasources(env_id, search)
|
||||
return datasets
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][list_datasources] Error: {e}")
|
||||
logger.explore(f"list_datasources failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(e))
|
||||
|
||||
@router.get("/datasources/{datasource_id}/columns", response_model=DatasourceColumnsResponse)
|
||||
@@ -224,9 +224,9 @@ async def get_datasource_columns(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get column metadata and database dialect for a Superset datasource."""
|
||||
logger.info(
|
||||
f"[translate_routes][get_datasource_columns] "
|
||||
f"Datasource: {datasource_id}, Env: {env_id}, User: {current_user.username}"
|
||||
logger.reason(
|
||||
f"get_datasource_columns — Datasource: {datasource_id}, Env: {env_id}, User: {current_user.username}",
|
||||
extra={"src": "translate_routes"}
|
||||
)
|
||||
try:
|
||||
result = fetch_datasource_columns_service(
|
||||
@@ -238,7 +238,7 @@ async def get_datasource_columns(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][get_datasource_columns] Error: {e}")
|
||||
logger.explore(f"get_datasource_columns failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"Failed to fetch datasource columns from Superset: {e}",
|
||||
|
||||
@@ -31,7 +31,7 @@ async def get_metrics(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get translation metrics."""
|
||||
logger.info(f"[translate_routes][get_metrics] User: {current_user.username}")
|
||||
logger.reason(f"get_metrics — User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
metrics_svc = TranslationMetrics(db)
|
||||
if job_id:
|
||||
@@ -54,7 +54,7 @@ async def get_job_metrics(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get aggregated metrics for a specific job."""
|
||||
logger.info(f"[translate_routes][get_job_metrics] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_job_metrics — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
metrics_svc = TranslationMetrics(db)
|
||||
return metrics_svc.get_job_metrics(job_id)
|
||||
|
||||
@@ -41,7 +41,7 @@ async def preview_translation(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Preview a translation before applying it."""
|
||||
logger.info(f"[translate_routes][preview_translation] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"preview_translation — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
if payload is None:
|
||||
payload = PreviewRequest()
|
||||
try:
|
||||
@@ -56,7 +56,7 @@ async def preview_translation(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][preview_translation] Error: {e}")
|
||||
logger.explore(f"preview_translation failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Preview failed: {e}")
|
||||
# #endregion preview_translation
|
||||
|
||||
@@ -76,7 +76,7 @@ async def update_preview_row(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Approve, edit, or reject a preview row."""
|
||||
logger.info(f"[translate_routes][update_preview_row] Job: {job_id}, Row: {row_key}, Action: {payload.action}")
|
||||
logger.reason(f"update_preview_row — Job: {job_id}, Row: {row_key}, Action: {payload.action}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
preview_service = TranslationPreview(db, config_manager, current_user.username)
|
||||
result = preview_service.update_preview_row(
|
||||
@@ -105,7 +105,7 @@ async def accept_preview_session(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Accept a preview session, enabling full translation execution."""
|
||||
logger.info(f"[translate_routes][accept_preview_session] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"accept_preview_session — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
preview_service = TranslationPreview(db, config_manager, current_user.username)
|
||||
result = preview_service.accept_preview_session(job_id=job_id)
|
||||
@@ -128,9 +128,9 @@ async def apply_preview(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Apply a preview session by session ID."""
|
||||
logger.info(f"[translate_routes][apply_preview] Session: {session_id}, User: {current_user.username}")
|
||||
logger.reason(f"apply_preview — Session: {session_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
# Find job_id from session
|
||||
from ...models.translate import TranslationPreviewSession
|
||||
from ....models.translate import TranslationPreviewSession
|
||||
session = db.query(TranslationPreviewSession).filter(TranslationPreviewSession.id == session_id).first()
|
||||
if not session:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Preview session '{session_id}' not found")
|
||||
@@ -159,9 +159,9 @@ async def get_preview_records(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get records for a preview session."""
|
||||
logger.info(f"[translate_routes][get_preview_records] Session: {session_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_preview_records — Session: {session_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
from ...models.translate import TranslationPreviewSession, TranslationPreviewRecord
|
||||
from ....models.translate import TranslationPreviewSession, TranslationPreviewRecord
|
||||
session = db.query(TranslationPreviewSession).filter(TranslationPreviewSession.id == session_id).first()
|
||||
if not session:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Preview session '{session_id}' not found")
|
||||
@@ -186,7 +186,7 @@ async def get_preview_records(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][get_preview_records] Error: {e}")
|
||||
logger.explore(f"get_preview_records failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion get_preview_records
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ async def list_runs(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""List all runs with cross-job filtering and pagination."""
|
||||
logger.info(f"[translate_routes][list_runs] User: {current_user.username}")
|
||||
logger.reason(f"list_runs — User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
from ...models.translate import TranslationRun
|
||||
from ....models.translate import TranslationRun
|
||||
query = db.query(TranslationRun)
|
||||
|
||||
if job_id:
|
||||
@@ -100,7 +100,7 @@ async def list_runs(
|
||||
|
||||
return {"items": items, "total": total, "page": page, "page_size": page_size}
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][list_runs] Error: {e}")
|
||||
logger.explore(f"list_runs failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion list_runs
|
||||
|
||||
@@ -122,7 +122,7 @@ async def get_run_detail(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get detailed run info with config_snapshot, records, events."""
|
||||
logger.info(f"[translate_routes][get_run_detail] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_run_detail — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
status_info = orch.get_run_status(run_id)
|
||||
@@ -131,7 +131,7 @@ async def get_run_detail(
|
||||
event_summary = TranslationEventLog(db).get_run_event_summary(run_id)
|
||||
|
||||
# Get config snapshot from run
|
||||
from ...models.translate import TranslationRun
|
||||
from ....models.translate import TranslationRun
|
||||
run = db.query(TranslationRun).filter(TranslationRun.id == run_id).first()
|
||||
config_snapshot = run.config_snapshot if run else None
|
||||
|
||||
@@ -164,9 +164,9 @@ async def download_skipped_csv(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Download skipped translation records as CSV."""
|
||||
logger.info(f"[translate_routes][download_skipped_csv] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"download_skipped_csv — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
from ...models.translate import TranslationRecord
|
||||
from ....models.translate import TranslationRecord
|
||||
from fastapi.responses import StreamingResponse
|
||||
import csv
|
||||
import io
|
||||
@@ -197,7 +197,7 @@ async def download_skipped_csv(
|
||||
headers={"Content-Disposition": f"attachment; filename=skipped_{run_id}.csv"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][download_skipped_csv] Error: {e}")
|
||||
logger.explore(f"download_skipped_csv failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion download_skipped_csv
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ async def run_translation(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Execute a translation job (trigger a run)."""
|
||||
logger.info(f"[translate_routes][run_translation] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"run_translation — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
run = orch.start_run(job_id=job_id, is_scheduled=False)
|
||||
@@ -50,7 +50,7 @@ async def run_translation(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][run_translation] Error: {e}")
|
||||
logger.explore(f"run_translation failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Run failed: {e}")
|
||||
# #endregion run_translation
|
||||
|
||||
@@ -68,7 +68,7 @@ async def retry_run(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Retry failed batches in a translation run."""
|
||||
logger.info(f"[translate_routes][retry_run] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"retry_run — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
run = orch.retry_failed_batches(run_id)
|
||||
@@ -76,7 +76,7 @@ async def retry_run(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][retry_run] Error: {e}")
|
||||
logger.explore(f"retry_run failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry failed: {e}")
|
||||
# #endregion retry_run
|
||||
|
||||
@@ -94,7 +94,7 @@ async def retry_insert(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Retry the SQL insert phase for a completed run."""
|
||||
logger.info(f"[translate_routes][retry_insert] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"retry_insert — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
run = orch.retry_insert(run_id)
|
||||
@@ -102,7 +102,7 @@ async def retry_insert(
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][retry_insert] Error: {e}")
|
||||
logger.explore(f"retry_insert failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Retry insert failed: {e}")
|
||||
# #endregion retry_insert
|
||||
|
||||
@@ -120,7 +120,7 @@ async def cancel_run(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Cancel a running translation."""
|
||||
logger.info(f"[translate_routes][cancel_run] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"cancel_run — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
run = orch.cancel_run(run_id)
|
||||
@@ -145,7 +145,7 @@ async def get_run_history(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get run history for a translation job."""
|
||||
logger.info(f"[translate_routes][get_run_history] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_run_history — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
total, runs = orch.get_run_history(job_id, page=page, page_size=page_size)
|
||||
@@ -168,7 +168,7 @@ async def get_run_status(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get status and statistics for a translation run."""
|
||||
logger.info(f"[translate_routes][get_run_status] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_run_status — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
return orch.get_run_status(run_id)
|
||||
@@ -193,7 +193,7 @@ async def get_run_records(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get paginated records for a translation run."""
|
||||
logger.info(f"[translate_routes][get_run_records] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_run_records — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
return orch.get_run_records(run_id, page=page, page_size=page_size, status_filter=status)
|
||||
@@ -218,9 +218,9 @@ async def get_batches(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get batches for a translation run."""
|
||||
logger.info(f"[translate_routes][get_batches] Run: {run_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_batches — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
from ...models.translate import TranslationBatch
|
||||
from ....models.translate import TranslationBatch
|
||||
batches = (
|
||||
db.query(TranslationBatch)
|
||||
.filter(TranslationBatch.run_id == run_id)
|
||||
@@ -243,7 +243,7 @@ async def get_batches(
|
||||
for b in batches
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"[translate_routes][get_batches] Error: {e}")
|
||||
logger.explore(f"get_batches failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion get_batches
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ async def get_schedule(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get schedule for a translation job."""
|
||||
logger.info(f"[translate_routes][get_schedule] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_schedule — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
scheduler = TranslationScheduler(db, config_manager, current_user.username)
|
||||
schedule = scheduler.get_schedule(job_id)
|
||||
@@ -69,11 +69,11 @@ async def set_schedule(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Set or update schedule for a translation job."""
|
||||
logger.info(f"[translate_routes][set_schedule] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"set_schedule — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
scheduler = TranslationScheduler(db, config_manager, current_user.username)
|
||||
# Check if schedule already exists
|
||||
from ...models.translate import TranslationSchedule
|
||||
from ....models.translate import TranslationSchedule
|
||||
existing = db.query(TranslationSchedule).filter(
|
||||
TranslationSchedule.job_id == job_id
|
||||
).first()
|
||||
@@ -129,7 +129,7 @@ async def enable_schedule(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Enable a schedule for a translation job."""
|
||||
logger.info(f"[translate_routes][enable_schedule] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"enable_schedule — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
scheduler = TranslationScheduler(db, config_manager, current_user.username)
|
||||
schedule = scheduler.set_schedule_active(job_id, is_active=True)
|
||||
@@ -160,7 +160,7 @@ async def disable_schedule(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Disable a schedule for a translation job."""
|
||||
logger.info(f"[translate_routes][disable_schedule] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"disable_schedule — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
scheduler = TranslationScheduler(db, config_manager, current_user.username)
|
||||
scheduler.set_schedule_active(job_id, is_active=False)
|
||||
@@ -186,7 +186,7 @@ async def delete_schedule(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Delete schedule for a translation job."""
|
||||
logger.info(f"[translate_routes][delete_schedule] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"delete_schedule — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
scheduler = TranslationScheduler(db, config_manager, current_user.username)
|
||||
scheduler.delete_schedule(job_id)
|
||||
@@ -213,7 +213,7 @@ async def get_next_executions(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Preview next N executions for a job's schedule."""
|
||||
logger.info(f"[translate_routes][get_next_executions] Job: {job_id}, User: {current_user.username}")
|
||||
logger.reason(f"get_next_executions — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
scheduler = TranslationScheduler(db, config_manager, current_user.username)
|
||||
schedule = scheduler.get_schedule(job_id)
|
||||
|
||||
Reference in New Issue
Block a user