feat: add month and year fields to master data model and implement optional direct Aeros API connectivity

main
Cizz22 3 months ago
parent bdf01fb947
commit 4ce108defa

@ -1,15 +1,42 @@
import anyio import anyio
from licaeros import LicensedSession, device_fingerprint_hex
from src.config import AEROS_BASE_URL, WINDOWS_AEROS_BASE_URL, VAULT_URL, ROLE_ID, SECRET_ID, AEROS_SECRET_PATH, AEROS_LICENSE_ID, AEROS_LICENSE_SECRET
import logging import logging
import httpx
from src.config import (
AEROS_BASE_URL,
WINDOWS_AEROS_BASE_URL,
VAULT_URL,
ROLE_ID,
SECRET_ID,
AEROS_SECRET_PATH,
AEROS_LICENSE_ID,
AEROS_LICENSE_SECRET,
USE_LICENSE_APP
)
from src.utils import get_vault_secrets from src.utils import get_vault_secrets
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Try to import licaeros gracefully
try:
from licaeros import LicensedSession, device_fingerprint_hex
HAS_LICAEROS = True
except ImportError:
HAS_LICAEROS = False
log.warning("licaeros library not found. Falling back to direct httpx calls if enabled.")
# Initialize a global session if possible, or create on demand # Initialize a global session if possible, or create on demand
_aeros_session = None _aeros_session = None
def get_aeros_session(base_url): def get_aeros_session(base_url):
global _aeros_session
if not USE_LICENSE_APP or not HAS_LICAEROS:
if _aeros_session is None or not isinstance(_aeros_session, httpx.Client):
log.info(f"Using direct httpx.Client for base URL: {base_url}")
_aeros_session = httpx.Client(base_url=base_url, timeout=300.0)
return _aeros_session
# License App path - only proceed if USE_LICENSE_APP is True
license_id = AEROS_LICENSE_ID license_id = AEROS_LICENSE_ID
license_secret = AEROS_LICENSE_SECRET license_secret = AEROS_LICENSE_SECRET
@ -30,10 +57,12 @@ def get_aeros_session(base_url):
log.warning("Failed to get Aeros license from Vault, trying local env fallback") log.warning("Failed to get Aeros license from Vault, trying local env fallback")
if not license_id or not license_secret: if not license_id or not license_secret:
raise Exception("Aeros license ID or Secret not provided (checked Vault and local .env)") log.warning("Aeros license ID or Secret not provided. Falling back to direct httpx.")
if _aeros_session is None or not isinstance(_aeros_session, httpx.Client):
_aeros_session = httpx.Client(base_url=base_url, timeout=300.0)
return _aeros_session
global _aeros_session if _aeros_session is None or isinstance(_aeros_session, httpx.Client):
if _aeros_session is None:
log.info(f"Initializing LicensedSession with base URL: {base_url}") log.info(f"Initializing LicensedSession with base URL: {base_url}")
log.info(f"Encrypted Device ID: {device_fingerprint_hex()}") log.info(f"Encrypted Device ID: {device_fingerprint_hex()}")
_aeros_session = LicensedSession( _aeros_session = LicensedSession(
@ -46,21 +75,48 @@ def get_aeros_session(base_url):
async def aeros_post(path: str, json=None, data=None, **kwargs): async def aeros_post(path: str, json=None, data=None, **kwargs):
""" """
Asynchronous wrapper for LicensedSession.post Asynchronous wrapper for Aeros requests (Licensed or Direct)
""" """
session = get_aeros_session(WINDOWS_AEROS_BASE_URL) # If not using license app, fetch from AEROS_BASE_URL direcly
url = f"/api/aeros{path}" target_base_url = WINDOWS_AEROS_BASE_URL if USE_LICENSE_APP and HAS_LICAEROS else AEROS_BASE_URL
# LicensedSession might not be async-compatible, so we run it in a thread session = get_aeros_session(target_base_url)
response = await anyio.to_thread.run_sync(
lambda: session.post(url, json_data=json, data=data, headers=kwargs.get("headers")) # Path logic: License app often uses /api/aeros prefix, direct might not.
) # However, to maintain compatibility with existing code calling this with path="/api/Project/ImportAROFile"
# we should decide if we need to adjust the path.
# User said: "system still fetch to aeros direcly not to licence app then lincense app forward it to aeros"
# This implies the license app acts as a proxy for /api/aeros/ paths.
if USE_LICENSE_APP and HAS_LICAEROS:
url = f"/api/aeros{path}"
response = await anyio.to_thread.run_sync(
lambda: session.post(url, json_data=json, data=data, headers=kwargs.get("headers"))
)
else:
# Direct fetch from Aeros might have different path structure?
# Assuming direct Aeros path is same as what the license app proxies to.
url = path
response = await anyio.to_thread.run_sync(
lambda: session.post(url, json=json, data=data, headers=kwargs.get("headers"))
)
return response return response
async def aeros_file_upload(path, file, field_name, filename): async def aeros_file_upload(path, file, field_name, filename):
session = get_aeros_session(WINDOWS_AEROS_BASE_URL) target_base_url = WINDOWS_AEROS_BASE_URL if USE_LICENSE_APP and HAS_LICAEROS else AEROS_BASE_URL
url = f"/api/aeros{path}" session = get_aeros_session(target_base_url)
response = await anyio.to_thread.run_sync(
lambda: session.post_multipart(url, file, field_name, filename) if USE_LICENSE_APP and HAS_LICAEROS:
) url = f"/api/aeros{path}"
response = await anyio.to_thread.run_sync(
lambda: session.post_multipart(url, file, field_name, filename)
)
else:
url = path
files = {field_name: (filename, file)}
response = await anyio.to_thread.run_sync(
lambda: session.post(url, files=files)
)
return response return response

@ -105,3 +105,5 @@ VAULT_URL=config('VAULT_URL', default=None)
ROLE_ID=config('ROLE_ID', default=None) ROLE_ID=config('ROLE_ID', default=None)
SECRET_ID=config('SECRET_ID', default=None) SECRET_ID=config('SECRET_ID', default=None)
AEROS_SECRET_PATH=config('AEROS_SECRET_PATH', default=None) AEROS_SECRET_PATH=config('AEROS_SECRET_PATH', default=None)
USE_LICENSE_APP = config("USE_LICENSE_APP", cast=bool, default=True)

@ -8,3 +8,5 @@ class RBDMasterData(Base, DefaultMixin):
name = Column(String(255), unique=True, nullable=False, index=True) name = Column(String(255), unique=True, nullable=False, index=True)
value_float = Column(Float, nullable=True) value_float = Column(Float, nullable=True)
value_string = Column(String(255), nullable=True) value_string = Column(String(255), nullable=True)
month = Column(String(20), nullable=True)
year = Column(String(20), nullable=True)

@ -35,6 +35,8 @@ async def get_dashboard_master_data(
): ):
from src.dashboard_model.service import get_master_data_list from src.dashboard_model.service import get_master_data_list
result = await get_master_data_list(db_session=db_session) result = await get_master_data_list(db_session=db_session)
return StandardResponse( return StandardResponse(
data=result, data=result,
message="Master data retrieved successfully" message="Master data retrieved successfully"

@ -1,14 +1,18 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
from src.models import DefultBase
from uuid import UUID
class RBDMasterDataSchema(BaseModel): class RBDMasterDataSchema(DefultBase):
model_config = ConfigDict(from_attributes=True) id: UUID
id: str
name: str name: str
value_float: Optional[float] = None value_float: Optional[float] = None
value_string: Optional[str] = None value_string: Optional[str] = None
month: Optional[str] = None
year: Optional[str] = None
class RBDMasterDataUpdate(BaseModel): class RBDMasterDataUpdate(DefultBase):
value_float: Optional[float] = None value_float: Optional[float] = None
value_string: Optional[str] = None value_string: Optional[str] = None
month: Optional[str] = None
year: Optional[str] = None

@ -53,6 +53,10 @@ async def update_master_data(db_session: DbSession, md_id: UUID, payload: RBDMas
record.value_float = payload.value_float record.value_float = payload.value_float
if payload.value_string is not None: if payload.value_string is not None:
record.value_string = payload.value_string record.value_string = payload.value_string
if payload.month is not None:
record.month = payload.month
if payload.year is not None:
record.year = payload.year
await db_session.commit() await db_session.commit()
await db_session.refresh(record) await db_session.refresh(record)
return record return record
@ -135,7 +139,9 @@ async def get_model_data(*, db_session: DbSession, simulation_id: Optional[UUID]
"Prediction_EAF": EAF, "Prediction_EAF": EAF,
"Prediction_EAF_KONKIN": EAF_KONKIN, "Prediction_EAF_KONKIN": EAF_KONKIN,
"Real_EAF": EAF_REAL, "Real_EAF": EAF_REAL,
"Real_EAF_KONKIN": EAF_KONKIN_REAL "Real_EAF_KONKIN": EAF_KONKIN_REAL,
"month": eaf_record.month,
"year": eaf_record.year
}, },
"Trip": { "Trip": {
"Prediction_Trip": Trip, "Prediction_Trip": Trip,

Loading…
Cancel
Save