diff --git a/.env b/.env index 5d5ce8a..865d7d2 100644 --- a/.env +++ b/.env @@ -16,5 +16,7 @@ COLLECTOR_CREDENTIAL_PASSWORD=postgres COLLECTOR_NAME=digital_aeros_fixed +AEROS_LICENSE_ID=20260218-Jre5VZieQfWXTq0G8ClpVSGszMf4UEUMLS5ENpWRVcoVSrNJckVZzXE +AEROS_LICENSE_SECRET=GmLIxf9fr8Ap5m1IYzkk4RPBFcm7UBvcd0eRdRQ03oRdxLHQA0d9oyhUk2ZlM3LVdRh1mkgYy5254bmCjFyWWc0oPFwNWYzNwDwnv50qy6SLRdaFnI0yZcfLbWQ7qCSj WINDOWS_AEROS_BASE_URL=http://192.168.1.102:8800 -TEMPORAL_URL=http://192.168.1.86:7233 \ No newline at end of file +TEMPORAL_URL=http://192.168.1.86:7233 diff --git a/poetry.lock b/poetry.lock index 187d26a..b97621a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1076,6 +1076,27 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "licaeros" +version = "0.1.0" +description = "" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "licaeros-0.1.0-cp310-cp310-linux_x86_64.whl", hash = "sha256:7ab820555c10edae6d8f391e71f0188463283ddbf60840a3baadb926bb78a6a9"}, + {file = "licaeros-0.1.0-cp311-cp311-linux_x86_64.whl", hash = "sha256:70b6b753c96e6fa4912e0e9eddf8088ddc3c4b6947ca16dc4b19047d2ee4aedf"}, + {file = "licaeros-0.1.0-cp312-cp312-linux_x86_64.whl", hash = "sha256:d89fe52a78637f2a72abf64f6a74445aad3899303cce7409f2d12fd277a0db00"}, +] + +[package.dependencies] +requests = "*" + +[package.source] +type = "legacy" +url = "https://git.reliabilityindonesia.com/api/packages/DigitalTwin/pypi/simple" +reference = "licaeros-repo" + [[package]] name = "limits" version = "3.13.0" @@ -2823,4 +2844,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = "^3.11" -content-hash = "9d61d2415a02d2e9a0be0850394cd0d8dbf178f7a419e726cc9b3b1c37d8d4d1" +content-hash = "f75002a661bdae021c2906c1b44ba85cfe1835d37378a717ee9561e376603771" diff --git a/pyproject.toml b/pyproject.toml index ef343a3..d36b34f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,14 @@ aiohttp = "^3.12.14" ijson = "^3.4.0" redis = "^7.1.0" clamd = "^1.0.2" +licaeros = "^0.1.0" +[[tool.poetry.source]] +name = "licaeros-repo" +url = "https://git.reliabilityindonesia.com/api/packages/DigitalTwin/pypi/simple/" +priority = "supplemental" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/src/aeros_equipment/router.py b/src/aeros_equipment/router.py index 7751fe1..0f473ca 100644 --- a/src/aeros_equipment/router.py +++ b/src/aeros_equipment/router.py @@ -14,7 +14,7 @@ from .service import save_default_equipment, get_all # from .schema import (OverhaulScheduleCreate, OverhaulSchedulePagination, OverhaulScheduleUpdate) -router = APIRouter() +router = APIRouter() @router.get("", response_model=StandardResponse[EquipmentPagination]) diff --git a/src/aeros_equipment/service.py b/src/aeros_equipment/service.py index 3c1c98a..6531941 100644 --- a/src/aeros_equipment/service.py +++ b/src/aeros_equipment/service.py @@ -7,7 +7,8 @@ from sqlalchemy import Delete, Select, func, desc, and_, text from sqlalchemy.orm import selectinload from src.auth.service import CurrentUser -from src.config import AEROS_BASE_URL, DEFAULT_PROJECT_NAME, RELIABILITY_SERVICE_API +from src.config import DEFAULT_PROJECT_NAME, RELIABILITY_SERVICE_API +from src.aeros_utils import aeros_post from src.database.core import CollectorDbSession, DbSession from src.database.service import search_filter_sort_paginate from .model import AerosEquipment, AerosEquipmentDetail, MasterEquipment, AerosEquipmentGroup, ReliabilityPredictNonRepairable @@ -21,7 +22,7 @@ import json import pandas as pd from src.aeros_simulation.service import get_aeros_schematic_by_name -client = httpx.AsyncClient(timeout=300.0) +# Aeros session is managed in src.aeros_utils log = logging.getLogger() async def get_project(*, db_session: DbSession): @@ -47,11 +48,13 @@ async def get_all(*, common): updateNodeReq = {"projectName": project.project_name , "equipmentNames": reg_nodes} try: - response = await client.post( - f"{AEROS_BASE_URL}/api/UpdateDisParams/GetUpdatedNodeDistributions", + response = await aeros_post( + "/api/UpdateDisParams/GetUpdatedNodeDistributions", json=updateNodeReq, headers={"Content-Type": "application/json"}, ) + + response.raise_for_status() res = response.json() @@ -96,8 +99,8 @@ async def get_by_id(*, db_session: DbSession, id: UUID): } try: - response = await client.post( - f"{AEROS_BASE_URL}/api/UpdateDisParams/GetUpdatedNodeDistributions", + response = await aeros_post( + "/api/UpdateDisParams/GetUpdatedNodeDistributions", json=aerosNodeReq, headers={"Content-Type": "application/json"}, ) @@ -124,8 +127,8 @@ async def get_aeros_equipment_by_location_tag(*, location_tag: Union[str, List[s } try: - response = await client.post( - f"{AEROS_BASE_URL}/api/UpdateDisParams/GetUpdatedNodeDistributions", + response = await aeros_post( + "/api/UpdateDisParams/GetUpdatedNodeDistributions", json=aerosNodeReq, headers={"Content-Type": "application/json"}, ) @@ -157,8 +160,8 @@ async def update_node( try: - response = await client.post( - f"{AEROS_BASE_URL}/api/UpdateDisParams/UpdateEquipmentDistributions", + response = await aeros_post( + "/api/UpdateDisParams/UpdateEquipmentDistributions", json=updateNodeReq, headers={"Content-Type": "application/json"}, ) @@ -188,8 +191,8 @@ async def save_default_equipment(*, db_session: DbSession, project_name: str): await db_session.execute(query) try: - response = await client.post( - f"{AEROS_BASE_URL}/api/UpdateDisParams/GetUpdatedNodeDistributions", + response = await aeros_post( + "/api/UpdateDisParams/GetUpdatedNodeDistributions", json=updateNodeReq, headers={"Content-Type": "application/json"}, ) diff --git a/src/aeros_project/service.py b/src/aeros_project/service.py index c57724d..8d97047 100644 --- a/src/aeros_project/service.py +++ b/src/aeros_project/service.py @@ -9,7 +9,8 @@ from sqlalchemy.orm import selectinload from src.aeros_equipment.service import save_default_equipment from src.aeros_simulation.service import save_default_simulation_node from src.auth.service import CurrentUser -from src.config import WINDOWS_AEROS_BASE_URL, AEROS_BASE_URL, CLAMAV_HOST, CLAMAV_PORT +from src.config import WINDOWS_AEROS_BASE_URL, CLAMAV_HOST, CLAMAV_PORT +from src.aeros_utils import aeros_post from src.database.core import DbSession from src.database.service import search_filter_sort_paginate from src.utils import sanitize_filename @@ -21,6 +22,8 @@ from .schema import AerosProjectInput, OverhaulScheduleCreate, OverhaulScheduleU import asyncio ALLOWED_EXTENSIONS = {".aro"} MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB +# client = httpx.AsyncClient(timeout=300.0) # Managed in src.aeros_utils +# We still use a local client for WINDOWS_AEROS_BASE_URL if it's not managed by aeros_utils client = httpx.AsyncClient(timeout=300.0) @@ -166,9 +169,9 @@ async def import_aro_project(*, db_session: DbSession, aeros_project_in: AerosPr # Update path to AEROS APP # Example BODy "C/dsad/dsad.aro" try: - response = await client.post( - f"{AEROS_BASE_URL}/api/Project/ImportAROFile", - content=f'"{aro_path}"', + response = await aeros_post( + "/api/Project/ImportAROFile", + data=f'"{aro_path}"', headers={"Content-Type": "application/json"}, ) response.raise_for_status() @@ -232,9 +235,9 @@ async def reset_project(*, db_session: DbSession): project = await fetch_aro_record(db_session=db_session) try: - response = await client.post( - f"{AEROS_BASE_URL}/api/Project/ImportAROFile", - content=f'"{project.aro_file_path}"', + response = await aeros_post( + "/api/Project/ImportAROFile", + data=f'"{project.aro_file_path}"', headers={"Content-Type": "application/json"}, ) response.raise_for_status() diff --git a/src/aeros_simulation/service.py b/src/aeros_simulation/service.py index 25fa070..fc382ce 100644 --- a/src/aeros_simulation/service.py +++ b/src/aeros_simulation/service.py @@ -8,11 +8,11 @@ import logging import httpx from fastapi import HTTPException, status import ijson -import requests from sqlalchemy import delete, desc, select, update, and_ from sqlalchemy.orm import selectinload -from src.config import AEROS_BASE_URL, AEROS_BASE_URL_OLD, DEFAULT_PROJECT_NAME +from src.config import DEFAULT_PROJECT_NAME +from src.aeros_utils import aeros_post from src.database.core import DbSession from src.database.service import CommonParameters, search_filter_sort_paginate from src.utils import save_to_pastebin @@ -32,7 +32,7 @@ from src.aeros_equipment.schema import EquipmentWithCustomParameters from .schema import SimulationInput, SimulationPlotResult, SimulationRankingParameters from .utils import calculate_eaf, stream_large_array -client = httpx.AsyncClient(timeout=300.0) +# client = httpx.AsyncClient(timeout=300.0) # Managed in src.aeros_utils active_simulations = {} @@ -399,11 +399,11 @@ async def execute_simulation(*, db_session: DbSession, simulation_id: Optional[U try: if not is_saved: - response = await client.post( - f"{AEROS_BASE_URL}/api/Simulation/RunSimulation", - json=sim_data, - headers={"Content-Type": "application/json"}, - ) + response = await aeros_post( + "/api/Simulation/RunSimulation", + json=sim_data, + headers={"Content-Type": "application/json"}, + ) response.raise_for_status() result = response.json() @@ -420,17 +420,15 @@ async def execute_simulation(*, db_session: DbSession, simulation_id: Optional[U print("Simulation started with id: %s", simulation.id) # if not os.path.exists(tmpfile): - response = requests.post(f"{AEROS_BASE_URL}/api/Simulation/RunSimulation", stream=True, json=sim_data) + # Using aeros_post wrapper which uses LicensedSession + response = await aeros_post( + "/api/Simulation/RunSimulation", + stream=True, + json=sim_data + ) file_obj = response.raw - # response = await client.post( - # f"{AEROS_BASE_URL}/api/Simulation/RunSimulation", - # json=sim_data, - # headers={"Content-Type": "application/json"}, - # ) - # response.raise_for_status() - # result = response.json() await save_simulation_result( db_session=db_session, simulation_id=simulation.id, schematic_name=sim_data["SchematicName"], eq_update=eq_update, file_path=file_obj diff --git a/src/aeros_simulation/simulation_save_service.py b/src/aeros_simulation/simulation_save_service.py index 28a68bd..2275d50 100644 --- a/src/aeros_simulation/simulation_save_service.py +++ b/src/aeros_simulation/simulation_save_service.py @@ -17,7 +17,7 @@ from fastapi import HTTPException, status from src.aeros_simulation.model import AerosSimulationCalcResult, AerosSimulationPlotResult from src.aeros_simulation.service import get_all_aeros_node, get_or_save_node, get_plant_calc_result, get_simulation_by_id, get_simulation_with_plot_result from src.aeros_simulation.utils import calculate_eaf, calculate_eaf_konkin -from src.config import AEROS_BASE_URL +from src.aeros_utils import aeros_post from src.database.core import DbSession @@ -32,17 +32,16 @@ async def execute_simulation( tmpfile = os.path.join(tempfile.gettempdir(), f"simulation_{simulation_id}.json") try: - async with httpx.AsyncClient(timeout=None) as client: - async with client.stream( - "POST", - f"{AEROS_BASE_URL}/api/Simulation/RunSimulation", - json=sim_data, - headers={"Content-Type": "application/json"}, - ) as response: - response.raise_for_status() - with open(tmpfile, "wb") as f: - async for chunk in response.aiter_bytes(): - f.write(chunk) + response = await aeros_post( + "/api/Simulation/RunSimulation", + json=sim_data, + headers={"Content-Type": "application/json"}, + stream=True + ) + response.raise_for_status() + with open(tmpfile, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) if not is_saved: # If not saving to DB, just return parsed JSON diff --git a/src/aeros_utils.py b/src/aeros_utils.py new file mode 100644 index 0000000..01800c0 --- /dev/null +++ b/src/aeros_utils.py @@ -0,0 +1,32 @@ +import anyio +from licaeros import LicensedSession, device_fingerprint_hex +from src.config import AEROS_BASE_URL, AEROS_LICENSE_ID, AEROS_LICENSE_SECRET +import logging + +log = logging.getLogger(__name__) + +# Initialize a global session if possible, or create on demand +_aeros_session = None + +def get_aeros_session(): + global _aeros_session + if _aeros_session is None: + log.info(f"Initializing LicensedSession with base URL: {AEROS_BASE_URL}") + log.info(f"Encrypted Device ID: {device_fingerprint_hex()}") + _aeros_session = LicensedSession( + api_base=AEROS_BASE_URL, + license_id=AEROS_LICENSE_ID, + license_secret=AEROS_LICENSE_SECRET, + ) + return _aeros_session + +async def aeros_post(path: str, json: dict = None, **kwargs): + """ + Asynchronous wrapper for LicensedSession.post + """ + session = get_aeros_session() + # LicensedSession might not be async-compatible, so we run it in a thread + response = await anyio.to_thread.run_sync( + lambda: session.post(path, json) + ) + return response diff --git a/src/config.py b/src/config.py index d9b5a5a..d30f24a 100644 --- a/src/config.py +++ b/src/config.py @@ -96,4 +96,7 @@ TEMPORAL_URL = config("TEMPORAL_URL", default="http://192.168.1.86:7233") RELIABILITY_SERVICE_API = config("RELIABILITY_SERVICE_API", default="http://192.168.1.82:8000/reliability") CLAMAV_HOST = config("CLAMAV_HOST", default="192.168.1.82") -CLAMAV_PORT = config("CLAMAV_PORT", cast=int, default=3310) \ No newline at end of file +CLAMAV_PORT = config("CLAMAV_PORT", cast=int, default=3310) + +AEROS_LICENSE_ID = config("AEROS_LICENSE_ID", default="") +AEROS_LICENSE_SECRET = config("AEROS_LICENSE_SECRET", default="") \ No newline at end of file