QTZPOC-20
QTZPOC-20: Refactor ModelImport status engine for lazy initialization, add byte-counted size validation to bundle extraction, optimize MLflow run ID lookups, and ensure workflow cancellation propagates correctly.
This commit is contained in:
@@ -72,24 +72,25 @@ def minio_client() -> MagicMock:
|
||||
def import_activity(tmp_path: Path, minio_client: MagicMock) -> ModelImport:
|
||||
repository = MagicMock()
|
||||
repository.minio_client = minio_client
|
||||
with patch('laborious.activities.model_import.create_engine', return_value=FakeStatusEngine()):
|
||||
activity = ModelImport(
|
||||
status_config={
|
||||
'host': 'h',
|
||||
'port': 5432,
|
||||
'dbname': 'sientia-core-mlops-bff',
|
||||
'user': 'u',
|
||||
'password': 'p',
|
||||
'schema': 'public',
|
||||
},
|
||||
import_config=import_config(tmp_path),
|
||||
minio_repository=repository,
|
||||
mlflow_repository=MagicMock(),
|
||||
mongo_repository=AsyncMock(),
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
activity = ModelImport(
|
||||
status_config={
|
||||
'host': 'h',
|
||||
'port': 5432,
|
||||
'dbname': 'sientia-core-mlops-bff',
|
||||
'user': 'u',
|
||||
'password': 'p',
|
||||
'schema': 'public',
|
||||
},
|
||||
import_config=import_config(tmp_path),
|
||||
minio_repository=repository,
|
||||
mlflow_repository=MagicMock(),
|
||||
mongo_repository=AsyncMock(),
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
# `status_engine` is created lazily on first access; pre-seed it with the recording double.
|
||||
activity._status_engine = FakeStatusEngine()
|
||||
activity.emit_metric = AsyncMock()
|
||||
activity.send_notification_async = AsyncMock()
|
||||
return activity
|
||||
|
||||
@@ -100,36 +100,37 @@ def import_activity(tmp_path: Path, mlflow_client: MagicMock, mongo: AsyncMock)
|
||||
minio_repository = MagicMock()
|
||||
mlflow_repository = MagicMock()
|
||||
mlflow_repository.client = mlflow_client
|
||||
with patch('laborious.activities.model_import.create_engine', return_value=FakeStatusEngine()):
|
||||
activity = ModelImport(
|
||||
status_config={
|
||||
'host': 'h',
|
||||
'port': 5432,
|
||||
'dbname': 'sientia-core-mlops-bff',
|
||||
'user': 'u',
|
||||
'password': 'p',
|
||||
'schema': 'public',
|
||||
},
|
||||
import_config={
|
||||
'password_key': None,
|
||||
'bucket': 'sientia',
|
||||
'prefix': 'imported_models/',
|
||||
'work_dir': str(tmp_path),
|
||||
'max_object_bytes': 1024,
|
||||
'max_entries': 10,
|
||||
'max_uncompressed_bytes': 4096,
|
||||
'max_compression_ratio': 200.0,
|
||||
'models_collection': 'models',
|
||||
'counters_collection': 'model_id_counters',
|
||||
'retention_days': 7,
|
||||
},
|
||||
minio_repository=minio_repository,
|
||||
mlflow_repository=mlflow_repository,
|
||||
mongo_repository=mongo,
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
activity = ModelImport(
|
||||
status_config={
|
||||
'host': 'h',
|
||||
'port': 5432,
|
||||
'dbname': 'sientia-core-mlops-bff',
|
||||
'user': 'u',
|
||||
'password': 'p',
|
||||
'schema': 'public',
|
||||
},
|
||||
import_config={
|
||||
'password_key': None,
|
||||
'bucket': 'sientia',
|
||||
'prefix': 'imported_models/',
|
||||
'work_dir': str(tmp_path),
|
||||
'max_object_bytes': 1024,
|
||||
'max_entries': 10,
|
||||
'max_uncompressed_bytes': 4096,
|
||||
'max_compression_ratio': 200.0,
|
||||
'models_collection': 'models',
|
||||
'counters_collection': 'model_id_counters',
|
||||
'retention_days': 7,
|
||||
},
|
||||
minio_repository=minio_repository,
|
||||
mlflow_repository=mlflow_repository,
|
||||
mongo_repository=mongo,
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
# `status_engine` is created lazily on first access; pre-seed it with the recording double.
|
||||
activity._status_engine = FakeStatusEngine()
|
||||
activity.emit_metric = AsyncMock()
|
||||
activity.send_notification_async = AsyncMock()
|
||||
return activity
|
||||
|
||||
@@ -164,17 +164,19 @@ def test_build_update_with_no_casts_or_extra_where_renders_a_bare_update():
|
||||
|
||||
def build_activity(engine: FakeStatusEngine) -> ModelImport:
|
||||
"""A `ModelImport` whose only connection is the recording engine double."""
|
||||
with patch('laborious.activities.model_import.create_engine', return_value=engine):
|
||||
activity = ModelImport(
|
||||
status_config=STATUS_CONFIG,
|
||||
import_config=IMPORT_CONFIG,
|
||||
minio_repository=MagicMock(),
|
||||
mlflow_repository=MagicMock(),
|
||||
mongo_repository=AsyncMock(),
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
activity = ModelImport(
|
||||
status_config=STATUS_CONFIG,
|
||||
import_config=IMPORT_CONFIG,
|
||||
minio_repository=MagicMock(),
|
||||
mlflow_repository=MagicMock(),
|
||||
mongo_repository=AsyncMock(),
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
# `status_engine` is created lazily on first access; setting the private attribute directly
|
||||
# pre-seeds it with the recording double instead of exercising the real `create_engine`.
|
||||
activity._status_engine = engine
|
||||
activity.emit_metric = AsyncMock()
|
||||
activity.send_notification_async = AsyncMock()
|
||||
return activity
|
||||
@@ -983,8 +985,62 @@ async def test_the_engine_is_disposed_on_shutdown(import_activity: ModelImport,
|
||||
@mark.asyncio
|
||||
async def test_close_is_a_no_op_without_an_engine(import_activity: ModelImport, engine):
|
||||
"""A partially-constructed instance (or a second close) must not blow up on a missing engine."""
|
||||
del import_activity.status_engine
|
||||
import_activity._status_engine = None
|
||||
|
||||
import_activity.close()
|
||||
|
||||
assert engine.disposed is False
|
||||
|
||||
|
||||
# ------------------------------------------------------------- the lazy status engine
|
||||
|
||||
|
||||
def test_no_engine_is_created_when_status_engine_is_never_accessed():
|
||||
"""A worker pod that never runs an import activity never opens a pool against the BFF Postgres."""
|
||||
with patch('laborious.activities.model_import.create_engine') as create_engine_mock:
|
||||
activity = ModelImport(
|
||||
status_config=STATUS_CONFIG,
|
||||
import_config=IMPORT_CONFIG,
|
||||
minio_repository=MagicMock(),
|
||||
mlflow_repository=MagicMock(),
|
||||
mongo_repository=AsyncMock(),
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
|
||||
create_engine_mock.assert_not_called()
|
||||
assert activity._status_engine is None
|
||||
|
||||
|
||||
def test_first_access_creates_the_engine_and_later_accesses_reuse_it():
|
||||
with patch(
|
||||
'laborious.activities.model_import.create_engine', return_value=FakeStatusEngine()
|
||||
) as create_engine_mock:
|
||||
activity = ModelImport(
|
||||
status_config=STATUS_CONFIG,
|
||||
import_config=IMPORT_CONFIG,
|
||||
minio_repository=MagicMock(),
|
||||
mlflow_repository=MagicMock(),
|
||||
mongo_repository=AsyncMock(),
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
|
||||
first = activity.status_engine
|
||||
second = activity.status_engine
|
||||
|
||||
create_engine_mock.assert_called_once()
|
||||
assert first is second
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_close_never_accessed_the_engine_does_not_create_one(import_activity: ModelImport):
|
||||
"""`close()` must not force the lazy engine into existence just to dispose it."""
|
||||
import_activity._status_engine = None
|
||||
|
||||
with patch('laborious.activities.model_import.create_engine') as create_engine_mock:
|
||||
import_activity.close()
|
||||
|
||||
create_engine_mock.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user