From ec5ab3468633a0f228d2722155fec4b55d2da4da Mon Sep 17 00:00:00 2001 From: Raykov-MS Date: Mon, 22 Jun 2026 08:03:22 +0300 Subject: [PATCH] fix --- app/pipeline/config.py | 31 +++++++++++++++++++++++-------- app/pipeline/file_manager.py | 18 +++++++++++++++--- app/pipeline/service.py | 14 ++++++++------ main.py | 14 +++----------- tests/smoke/test_batch_smoke.py | 2 +- 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/app/pipeline/config.py b/app/pipeline/config.py index c8555cd..28cfc7c 100644 --- a/app/pipeline/config.py +++ b/app/pipeline/config.py @@ -20,17 +20,18 @@ def validate_file_name(file_name: str) -> bool: return any(pattern.match(file_name) for pattern in FILE_NAME_PATTERNS) -def build_date_parts(now: datetime | None = None) -> tuple[str, str, str]: +def build_date_parts(now: datetime | None = None) -> tuple[str, str, str, str]: current = now or datetime.now() - year = current.strftime("%Y") - month_year = current.strftime("%m_%Y") + year = str(current.year) + month = str(current.month) + day = str(current.day) date_key = current.strftime("%Y%m%d") - return year, month_year, date_key + return year, month, day, date_key def build_out_dir(base_dir: str, now: datetime | None = None) -> str: - year, month_year, date_key = build_date_parts(now) - return join_path(base_dir, DEFAULT_OUTPUT_DIR_NAME, year, month_year, date_key) + year, month, day, _ = build_date_parts(now) + return join_path(base_dir, DEFAULT_OUTPUT_DIR_NAME, year, month, day) def build_run_dir(base_dir: str, run_number: int, now: datetime | None = None) -> str: @@ -59,8 +60,22 @@ def build_process_log_file_name(date_key: str, run_number: int) -> str: def join_path(*parts: str) -> str: - cleaned = [part.strip("/\\") for part in parts if part.strip("/\\")] - return "\\".join(cleaned) + raw_parts = [part for part in parts if part and part.strip("/\\")] + if not raw_parts: + return "" + + first = raw_parts[0] + is_unc = ( + first.startswith("//") + or first.startswith("\\\\") + or first.startswith("/") + or first.startswith("\\") + ) + cleaned = [part.replace("/", "\\").strip("\\") for part in raw_parts] + joined = "\\".join(cleaned) + if is_unc: + return "\\\\" + joined.lstrip("\\") + return joined def get_parent_dir(path: str) -> str: diff --git a/app/pipeline/file_manager.py b/app/pipeline/file_manager.py index 1f8de2a..2b2ddc1 100644 --- a/app/pipeline/file_manager.py +++ b/app/pipeline/file_manager.py @@ -67,8 +67,10 @@ class SMBClientConnection: def listdir(self, path: str) -> list[str]: smbclient = _import_smbclient() full_path = self._resolve_path(path) - with smbclient.scandir(full_path) as entries: - return [entry.name for entry in entries] + # In some smbclient/smbprotocol versions scandir() returns a generator, + # not a context manager, so we iterate directly. + entries = smbclient.scandir(full_path) + return [entry.name for entry in entries] def is_file(self, path: str) -> bool: smbclient = _import_smbclient() @@ -168,6 +170,8 @@ class SMBClientConnection: def _resolve_path(self, path: str) -> str: if path.startswith("//") or path.startswith("\\\\"): return _normalize_unc_path(path) + if _looks_like_unc_without_prefix(path, self.smb_base_path): + return _normalize_unc_path("//" + path.lstrip("/\\")) return _normalize_unc_path(join_path(self.smb_base_path, path)) @@ -207,7 +211,7 @@ def create_run_context( run_number = next_run_number(out_day_dir, samba_conn) run_dir = join_path(out_day_dir, str(run_number)) ensure_dir_exists(run_dir, samba_conn) - _, _, date_key = build_date_parts(now) + _, _, _, date_key = build_date_parts(now) return RunContext( run_dir=run_dir, out_day_dir=out_day_dir, @@ -384,3 +388,11 @@ def _import_smbclient(): except ImportError as exc: # noqa: BLE001 raise RuntimeError("Библиотека smbprotocol/smbclient не установлена.") from exc return smbclient + + +def _looks_like_unc_without_prefix(path: str, smb_base_path: str) -> bool: + normalized_path = path.replace("/", "\\").lstrip("\\") + if not normalized_path: + return False + server = _extract_server(smb_base_path).lower() + return normalized_path.lower().startswith(f"{server}\\") diff --git a/app/pipeline/service.py b/app/pipeline/service.py index 66f319e..184656a 100644 --- a/app/pipeline/service.py +++ b/app/pipeline/service.py @@ -25,6 +25,8 @@ from .parser import FileParseResult, parse_xml_content from .protocol import ProtocolRow, write_protocol from .report import ReportRow, write_report +EMPTY_BATCH_STATUS = "info" + @dataclass class BatchResult: @@ -84,7 +86,7 @@ def run_batch( scan_result = scan_input_files(input_dir=input_dir, samba_conn=samba_conn) log_lines.append( - f"{_now()} scan files: valid={len(scan_result.valid_files)} invalid={len(scan_result.invalid_files)}" + f"{_now()} | scan files: valid={len(scan_result.valid_files)} invalid={len(scan_result.invalid_files)}" ) for remote_file in scan_result.invalid_files: @@ -106,7 +108,7 @@ def run_batch( processed_at=datetime.now(), ) ) - log_lines.append(f"{_now()} invalid -> err: {remote_file.path} => {moved_to}") + log_lines.append(f"{_now()} | invalid -> err: {remote_file.path} => {moved_to}") for remote_file in scan_result.valid_files: parse_result = _parse_remote_xml(remote_file.name, remote_file.path, samba_conn) @@ -131,7 +133,7 @@ def run_batch( samba_conn=samba_conn, ) log_lines.append( - f"{_now()} success -> processed: {remote_file.path} => {moved_to}" + f"{_now()} | success -> processed: {remote_file.path} => {moved_to}" ) else: moved_to = move_file_to_error( @@ -141,14 +143,14 @@ def run_batch( samba_conn=samba_conn, ) log_lines.append( - f"{_now()} {status} -> err: {remote_file.path} => {moved_to}" + f"{_now()} | {status} -> err: {remote_file.path} => {moved_to}" ) if not protocol_rows: protocol_rows.append( ProtocolRow( file_name="-", - status="success", + status=EMPTY_BATCH_STATUS, message="Новых файлов для обработки нет.", operations_total=0, rows_written=0, @@ -156,7 +158,7 @@ def run_batch( processed_at=datetime.now(), ) ) - log_lines.append(f"{_now()} empty batch") + log_lines.append(f"{_now()} | empty batch") report_file_name = build_report_file_name( run_context.date_key, run_context.run_number diff --git a/main.py b/main.py index e79b7ee..4c29118 100644 --- a/main.py +++ b/main.py @@ -43,16 +43,7 @@ def main() -> None: help="Пароль учетной записи для доступа к SMB-шаре.", ) if st.button("Изменить путь", use_container_width=True): - ok, message, _ = check_access_and_list_files( - smb_base_path=SFM_SMB_BASE_PATH, - input_dir=resolved_input_dir, - samba_user=samba_user, - samba_password=samba_password, - ) - if ok: - open_path_editor(current_input_dir) - else: - st.error(f"{message} Обратитесь к {ACCESS_SUPPORT_CONTACT}.") + open_path_editor(current_input_dir) edited_path = render_path_editor() if edited_path is not None: @@ -78,12 +69,13 @@ def main() -> None: use_container_width=True, ): with st.spinner("Выполняется обработка..."): + launcher_name = samba_user.strip() or user_id batch_result = run_batch( smb_base_path=SFM_SMB_BASE_PATH, input_dir=resolved_input_dir, samba_user=samba_user, samba_password=samba_password, - launcher=user_id, + launcher=launcher_name, ) if batch_result.status == "success": st.success("Обработка завершена успешно.") diff --git a/tests/smoke/test_batch_smoke.py b/tests/smoke/test_batch_smoke.py index 3b921e3..78e9d6e 100644 --- a/tests/smoke/test_batch_smoke.py +++ b/tests/smoke/test_batch_smoke.py @@ -112,4 +112,4 @@ def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None: launcher="tester", ) assert second.status == "success" - assert second.processed >= 1 + assert second.processed == 0