From 5e217c7cce3576d17f613076b1c904b179155f74 Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 1 Sep 2025 19:19:28 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_parser/core/ports.py | 89 ++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/python_parser/core/ports.py b/python_parser/core/ports.py index 4519374..c3a5c67 100644 --- a/python_parser/core/ports.py +++ b/python_parser/core/ports.py @@ -2,28 +2,93 @@ Порты (интерфейсы) для hexagonal architecture """ from abc import ABC, abstractmethod -from typing import Optional +from typing import Optional, Dict, List, Any, Callable import pandas as pd class ParserPort(ABC): - """Интерфейс для парсеров""" + """Интерфейс для парсеров с поддержкой множественных геттеров""" + + def __init__(self): + """Инициализация с пустым словарем геттеров""" + self.getters: Dict[str, Dict[str, Any]] = {} + self._register_default_getters() + + def _register_default_getters(self): + """Регистрация геттеров по умолчанию - переопределяется в наследниках""" + pass + + def register_getter(self, name: str, method: Callable, required_params: List[str], + optional_params: List[str] = None, description: str = ""): + """ + Регистрация нового геттера + + Args: + name: Имя геттера + method: Метод для выполнения + required_params: Список обязательных параметров + optional_params: Список необязательных параметров + description: Описание геттера + """ + if optional_params is None: + optional_params = [] + + self.getters[name] = { + "method": method, + "required_params": required_params, + "optional_params": optional_params, + "description": description + } + + def get_available_getters(self) -> Dict[str, Dict[str, Any]]: + """Получение списка доступных геттеров с их описанием""" + return { + name: { + "required_params": info["required_params"], + "optional_params": info["optional_params"], + "description": info["description"] + } + for name, info in self.getters.items() + } + + # Добавить схему + def get_value(self, getter_name: str, params: Dict[str, Any]): + """ + Получение значения через указанный геттер + + Args: + getter_name: Имя геттера + params: Параметры для геттера + + Returns: + Результат выполнения геттера + + Raises: + ValueError: Если геттер не найден или параметры неверны + """ + if getter_name not in self.getters: + available = list(self.getters.keys()) + raise ValueError(f"Геттер '{getter_name}' не найден. Доступные: {available}") + + getter_info = self.getters[getter_name] + required = getter_info["required_params"] + + # Проверка обязательных параметров + missing = [p for p in required if p not in params] + if missing: + raise ValueError(f"Отсутствуют обязательные параметры для геттера '{getter_name}': {missing}") + + # Вызов метода геттера + try: + return getter_info["method"](params) + except Exception as e: + raise ValueError(f"Ошибка выполнения геттера '{getter_name}': {str(e)}") @abstractmethod def parse(self, file_path: str, params: dict) -> pd.DataFrame: """Парсинг файла и возврат DataFrame""" pass - @abstractmethod - def get_value(self, df: pd.DataFrame, params: dict): - """Получение значения из DataFrame по параметрам""" - pass - - # @abstractmethod - # def get_schema(self) -> dict: - # """Возвращает схему входных параметров для парсера""" - # pass - class StoragePort(ABC): """Интерфейс для хранилища данных""" From 46a30c32ed14afd7138a9c16a69c8461128dd27f Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 1 Sep 2025 19:20:16 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_parser/core/services.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/python_parser/core/services.py b/python_parser/core/services.py index 0d3f791..16e7da0 100644 --- a/python_parser/core/services.py +++ b/python_parser/core/services.py @@ -99,9 +99,35 @@ class ReportService: # Получаем парсер parser = get_parser(request.report_type) + + # Устанавливаем DataFrame в парсер для использования в геттерах + parser.df = df - # Получаем значение - value = parser.get_value(df, request.get_params) + # Получаем параметры запроса + get_params = request.get_params or {} + + # Определяем имя геттера (по умолчанию используем первый доступный) + getter_name = get_params.pop("getter", None) + if not getter_name: + # Если геттер не указан, берем первый доступный + available_getters = list(parser.getters.keys()) + if available_getters: + getter_name = available_getters[0] + print(f"⚠️ Геттер не указан, используем первый доступный: {getter_name}") + else: + return DataResult( + success=False, + message="Парсер не имеет доступных геттеров" + ) + + # Получаем значение через указанный геттер + try: + value = parser.get_value(getter_name, get_params) + except ValueError as e: + return DataResult( + success=False, + message=f"Ошибка параметров: {str(e)}" + ) # Формируем результат if value is not None: From 72fe115a99ee00666c5cd3e5fc2e23fb5762bcb0 Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 1 Sep 2025 19:22:58 +0300 Subject: [PATCH 3/3] main --- python_parser/app/main.py | 164 ++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 58 deletions(-) diff --git a/python_parser/app/main.py b/python_parser/app/main.py index 8f6e7e8..578d06a 100644 --- a/python_parser/app/main.py +++ b/python_parser/app/main.py @@ -96,6 +96,54 @@ async def get_available_parsers(): return {"parsers": parsers} +@app.get("/parsers/{parser_name}/getters", tags=["Общее"], + summary="Информация о геттерах парсера", + description="Возвращает информацию о доступных геттерах для указанного парсера", + responses={ + 200: { + "content": { + "application/json": { + "example": { + "parser": "svodka_pm", + "getters": { + "single_og": { + "required_params": ["id", "codes", "columns"], + "optional_params": ["search"], + "description": "Получение данных по одному ОГ" + }, + "total_ogs": { + "required_params": ["codes", "columns"], + "optional_params": ["search"], + "description": "Получение данных по всем ОГ" + } + } + } + } + } + }, + 404: { + "description": "Парсер не найден" + } + }) +async def get_parser_getters(parser_name: str): + """Получение информации о геттерах парсера""" + if parser_name not in PARSERS: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Парсер '{parser_name}' не найден" + ) + + parser_class = PARSERS[parser_name] + parser_instance = parser_class() + + getters_info = parser_instance.get_available_getters() + + return { + "parser": parser_name, + "getters": getters_info + } + + @app.get("/server-info", tags=["Общее"], summary="Информация о сервере", response_model=ServerInfoResponse,) @@ -352,40 +400,40 @@ async def get_svodka_pm_total_ogs( raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") -# @app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name]) -# async def get_svodka_pm_data( -# request_data: dict -# ): -# report_service = get_report_service() -# """ -# Получение данных из отчета сводки факта СарНПЗ +@app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name]) +async def get_svodka_pm_data( + request_data: dict +): + report_service = get_report_service() + """ + Получение данных из отчета сводки факта СарНПЗ -# - indicator_id: ID индикатора -# - code: Код для поиска -# - search_value: Опциональное значение для поиска -# """ -# try: -# # Создаем запрос -# request = DataRequest( -# report_type='svodka_pm', -# get_params=request_data -# ) + - indicator_id: ID индикатора + - code: Код для поиска + - search_value: Опциональное значение для поиска + """ + try: + # Создаем запрос + request = DataRequest( + report_type='svodka_pm', + get_params=request_data + ) -# # Получаем данные -# result = report_service.get_data(request) + # Получаем данные + result = report_service.get_data(request) -# if result.success: -# return { -# "success": True, -# "data": result.data -# } -# else: -# raise HTTPException(status_code=404, detail=result.message) + if result.success: + return { + "success": True, + "data": result.data + } + else: + raise HTTPException(status_code=404, detail=result.message) -# except HTTPException: -# raise -# except Exception as e: -# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") @app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name], @@ -562,38 +610,38 @@ async def get_svodka_ca_data( # raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") -# @app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name]) -# async def get_monitoring_fuel_data( -# request_data: dict -# ): -# report_service = get_report_service() -# """ -# Получение данных из отчета мониторинга топлива +@app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name]) +async def get_monitoring_fuel_data( + request_data: dict +): + report_service = get_report_service() + """ + Получение данных из отчета мониторинга топлива -# - column: Название колонки для агрегации (normativ, total, total_svod) -# """ -# try: -# # Создаем запрос -# request = DataRequest( -# report_type='monitoring_fuel', -# get_params=request_data -# ) + - column: Название колонки для агрегации (normativ, total, total_svod) + """ + try: + # Создаем запрос + request = DataRequest( + report_type='monitoring_fuel', + get_params=request_data + ) -# # Получаем данные -# result = report_service.get_data(request) + # Получаем данные + result = report_service.get_data(request) -# if result.success: -# return { -# "success": True, -# "data": result.data -# } -# else: -# raise HTTPException(status_code=404, detail=result.message) + if result.success: + return { + "success": True, + "data": result.data + } + else: + raise HTTPException(status_code=404, detail=result.message) -# except HTTPException: -# raise -# except Exception as e: -# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") # @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name])