diff --git a/src/omotes_sdk/prefect_util.py b/src/omotes_sdk/prefect_util.py index 89ec0c8..61f7d13 100644 --- a/src/omotes_sdk/prefect_util.py +++ b/src/omotes_sdk/prefect_util.py @@ -210,9 +210,13 @@ def write_flow_return_artifact_to_minio( minio_port: str, access_key: str, secret_key: str, + minio_host_external: str, ) -> str | None: """Persist flow return fields to MinIO and publish Prefect links to those objects. + ``minio_host`` is used for storage operations from the worker, while + ``minio_host_external`` is used to generate browser-accessible URLs. + Returns: str | None: Run folder path in MinIO, or None if not in flow context. @@ -221,6 +225,7 @@ def write_flow_return_artifact_to_minio( return None minio_block = _build_minio_result_storage(minio_host, minio_port, access_key, secret_key) + external_minio_block = _build_minio_result_storage(minio_host_external, minio_port, access_key, secret_key) run_folder_path = _sanitize_for_minio(f"{flow_run.get_name()}-{_get_flow_run_id_first_part()}") for field_name, field_value in flow_result: @@ -243,7 +248,7 @@ def write_flow_return_artifact_to_minio( logging.exception("Failed to persist flow return field '%s' to MinIO", field_name) continue - presigned_url = _create_minio_presigned_url(minio_block, field_object_path) + presigned_url = _create_minio_presigned_url(external_minio_block, field_object_path) if presigned_url is None: continue diff --git a/tests/test_prefect_util.py b/tests/test_prefect_util.py index 5d17657..e8254b7 100644 --- a/tests/test_prefect_util.py +++ b/tests/test_prefect_util.py @@ -1,7 +1,7 @@ # ruff: noqa: D103 import asyncio -from typing import cast +from typing import Any, cast import pytest from prefect.states import StateType @@ -11,6 +11,7 @@ from omotes_sdk.memory_quantity import _memory_quantity_to_bytes from omotes_sdk.prefect_util import ( _build_universal_job_vars, + _create_minio_presigned_url, _get_required_file_extension, _is_semantic_version, _resolve_artifact_data, @@ -220,6 +221,29 @@ def test_resolve_artifact_data_returns_original_for_non_json_string() -> None: assert asyncio.run(_resolve_artifact_data(raw_data, "minio.example.com", 9000, "key", "secret")) == raw_data +def test_create_minio_presigned_url_uses_external_block() -> None: + class _FileSystem: + def sign(self, path: str, expiration: int) -> str: + assert path == "prefect-results/flow-results/result.json" + assert expiration == 60 + return "https://minio.example.com/result.json" + + class _MinioBlock: + filesystem = _FileSystem() + + def _resolve_path(self, object_path: str) -> str: + return f"prefect-results/flow-results/{object_path}" + + assert ( + _create_minio_presigned_url( + cast(Any, _MinioBlock()), + "result.json", + expires_seconds=60, + ) + == "https://minio.example.com/result.json" + ) + + def test_get_runs_returns_all_flow_runs(monkeypatch: pytest.MonkeyPatch) -> None: expected_runs = ["run-1", "run-2"]