From f952647cf65b78d1f866fd8a89ee2a398d3038b2 Mon Sep 17 00:00:00 2001 From: Bruno Domingues Date: Thu, 19 Feb 2026 17:56:08 -0300 Subject: [PATCH 1/2] SIENTIAPDE-1579: Update MLflow to 2.18.0 and add setuptools version constraint. --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 362a16b..4a28cc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,8 @@ boto3==1.40.55 botocore==1.40.55 git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.6.1 prometheus-client==0.23.1 -mlflow==2.10.1 +mlflow==2.18.0 +setuptools<81 evidently==0.4.21 beautifulsoup4==4.12.3 scikit-learn==1.4.2 From 41c4490cad373ee3dcdc400a0aa37ca07a88652e Mon Sep 17 00:00:00 2001 From: Bruno Domingues Date: Thu, 19 Feb 2026 21:13:41 -0300 Subject: [PATCH 2/2] SIENTIAPDE-1579: Fix SientiaMlException propagation in ModelServing.search_runs_by_name to correctly raise the exception with its message, resolving a TypeError. Enhance training test script to support scenario-specific CSV files for different test cases. Update search_runs_by_name return type hint and apply minor code formatting. --- model_manager/sientia/model_serving.py | 6 +- model_manager/sientia/models.py | 4 +- scripts/run_training_test.py | 15 +++++ tests/sientia/test_model_serving.py | 8 +-- .../repository/test_training_repository.py | 56 +++++++++++-------- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/model_manager/sientia/model_serving.py b/model_manager/sientia/model_serving.py index fe3b560..160df07 100644 --- a/model_manager/sientia/model_serving.py +++ b/model_manager/sientia/model_serving.py @@ -56,7 +56,7 @@ class ModelServing: # Function to list runs for a given experiment def search_runs_by_name( self, experiment_names: list[str], order_by: None | list[str] = None - ) -> pd.DataFrame: + ) -> pd.DataFrame | list: """ List runs for a specified MLflow experiment. @@ -64,7 +64,7 @@ class ModelServing: experiment_names (list[str]): List with experiment_names to retrieve runs from. Returns: - pandas.DataFrame: A DataFrame containing run information. + Union[pd.DataFrame, list]: A DataFrame or list containing run information. Raises: SientiaMlException: If unable to search runs. @@ -73,7 +73,7 @@ class ModelServing: runs = mlflow.search_runs(experiment_names=experiment_names, order_by=order_by) except SientiaMlException as e: logging.error(e) - raise SientiaMlException from e + raise SientiaMlException(str(e)) from e return runs def set_experiment(self, experiment_identifier: str) -> None: diff --git a/model_manager/sientia/models.py b/model_manager/sientia/models.py index 677caec..80b38c8 100644 --- a/model_manager/sientia/models.py +++ b/model_manager/sientia/models.py @@ -35,9 +35,7 @@ def validate_frontend_date_format(fmt: str | None) -> None: return if fmt not in ALLOWED_FRONTEND_DATE_FORMATS: allowed = ', '.join(sorted(ALLOWED_FRONTEND_DATE_FORMATS)) - raise ValueError( - f'Invalid date_format "{fmt}". Allowed formats: {allowed}' - ) + raise ValueError(f'Invalid date_format "{fmt}". Allowed formats: {allowed}') def _frontend_date_format_to_strftime(fmt: str | None) -> str | None: diff --git a/scripts/run_training_test.py b/scripts/run_training_test.py index d98d41a..a0ecfe0 100644 --- a/scripts/run_training_test.py +++ b/scripts/run_training_test.py @@ -51,6 +51,12 @@ TEST_SCENARIOS_DIR = PROJECT_ROOT / 'docs' / 'test-scenarios' MINIO_ALIAS = 'suse' MINIO_BUCKET = 'model-training' +# Mapeamento de CSV específico por cenário +SCENARIO_CSV_MAPPING = { + '12-angular-test-date-format': Path('docs/DB_CV022_WIT230.csv'), + '13-angular-test-double-date-column': Path('docs/DB_CV022_WIT230 _double_date_column.csv'), +} + POSTGRES_CONFIG = { 'host': os.getenv('POSTGRES_HOST'), 'port': os.getenv('POSTGRES_PORT'), @@ -306,6 +312,10 @@ def run_local_pipeline( result['error'] = str(exc) return result + # Use scenario-specific CSV if mapped, otherwise use provided csv_path + if scenario_name in SCENARIO_CSV_MAPPING: + csv_path = SCENARIO_CSV_MAPPING[scenario_name] + csv_path = _resolve_csv_path(csv_path) _ensure_source_file(csv_path) payload = _build_local_payload(request_data, csv_path) @@ -450,6 +460,11 @@ def run_single_scenario(scenario_name: str, csv_path: Path) -> dict: result['error'] = str(exc) return result + # Use scenario-specific CSV if mapped, otherwise use provided csv_path + if scenario_name in SCENARIO_CSV_MAPPING: + csv_path = SCENARIO_CSV_MAPPING[scenario_name] + print(f' Using scenario-specific CSV: {csv_path}') + csv_path = _resolve_csv_path(csv_path) # Upload CSV to MinIO diff --git a/tests/sientia/test_model_serving.py b/tests/sientia/test_model_serving.py index c50dab7..adfab27 100644 --- a/tests/sientia/test_model_serving.py +++ b/tests/sientia/test_model_serving.py @@ -112,18 +112,16 @@ def test_search_runs_by_name_with_order_by(mock_search_runs, mock_set_tracking_u def test_search_runs_by_name_raises_exception( mock_logging_error, mock_search_runs, mock_set_tracking_uri ): - """Test search_runs_by_name raises TypeError due to bug in line 80 of model_serving.py.""" + """Test search_runs_by_name properly propagates SientiaMlException.""" model_serving = ModelServing(tracking_uri='http://mlflow.example.com') exception = SientiaMlException(message='Search failed') mock_search_runs.side_effect = exception - # The code has a bug on line 80: "raise SientiaMlException from e" - # This raises TypeError because SientiaMlException requires 'message' argument - with raises(TypeError, match="missing 1 required positional argument: 'message'"): + with raises(SientiaMlException, match='Search failed'): model_serving.search_runs_by_name(['experiment1']) - mock_logging_error.assert_called_once() + mock_logging_error.assert_called_once_with(exception) @patch('model_manager.sientia.model_serving.mlflow.set_tracking_uri') diff --git a/tests/utils/repository/test_training_repository.py b/tests/utils/repository/test_training_repository.py index e33384b..e500934 100644 --- a/tests/utils/repository/test_training_repository.py +++ b/tests/utils/repository/test_training_repository.py @@ -993,7 +993,9 @@ class TestConfigureDatetimeIndex: assert 'var2' in result.columns @pytest.mark.filterwarnings('ignore::UserWarning') - def test_configure_datetime_index_invalid_timestamp_column(self, training_repo, datetime_params): + def test_configure_datetime_index_invalid_timestamp_column( + self, training_repo, datetime_params + ): """Test _configure_datetime_index with invalid timestamp values.""" data = pd.DataFrame( { @@ -1199,10 +1201,12 @@ class TestEnsureDateColumnParsed: def test_parses_column_with_format(self, date_params): """When date_column and date_format set, column is parsed as datetime.""" - data = pd.DataFrame({ - 'ts': ['2023-01-01 10:00:00', '2023-06-15 14:30:00'], - 'x': [1, 2], - }) + data = pd.DataFrame( + { + 'ts': ['2023-01-01 10:00:00', '2023-06-15 14:30:00'], + 'x': [1, 2], + } + ) result = _ensure_date_column_parsed(data, date_params) assert result['ts'].dtype == 'datetime64[ns]' assert result['ts'].iloc[0].year == 2023 @@ -1212,10 +1216,12 @@ class TestEnsureDateColumnParsed: def test_invalid_values_coerced_to_nat(self, date_params): """Invalid date strings are coerced to NaT when format is set.""" date_params.date_format = 'yyyy-MM-dd HH:mm:ss' - data = pd.DataFrame({ - 'ts': ['2023-01-01 00:00:00', 'not-a-date', '2023-12-31 00:00:00'], - 'x': [1, 2, 3], - }) + data = pd.DataFrame( + { + 'ts': ['2023-01-01 00:00:00', 'not-a-date', '2023-12-31 00:00:00'], + 'x': [1, 2, 3], + } + ) result = _ensure_date_column_parsed(data, date_params) assert pd.isna(result['ts'].iloc[1]) assert result['ts'].iloc[0].year == 2023 @@ -1256,10 +1262,12 @@ class TestApplySupportFilters: def test_snake_case_upper_lower_line(self): """Support filters with upper_line/lower_line (snake_case) filter rows.""" - data = pd.DataFrame({ - 'x': [1.0, 2.0, 3.0, 4.0], - 'target': [2.0, 4.0, 6.0, 8.0], - }) + data = pd.DataFrame( + { + 'x': [1.0, 2.0, 3.0, 4.0], + 'target': [2.0, 4.0, 6.0, 8.0], + } + ) support_filters = { 'x': { 'upper_line': {'intercept': 1.0, 'angle': 50}, @@ -1272,10 +1280,12 @@ class TestApplySupportFilters: def test_camel_case_upper_lower_line(self): """Support filters with upperLine/lowerLine (camelCase) are accepted.""" - data = pd.DataFrame({ - 'x': [1.0, 2.0, 3.0], - 'target': [1.0, 2.0, 3.0], - }) + data = pd.DataFrame( + { + 'x': [1.0, 2.0, 3.0], + 'target': [1.0, 2.0, 3.0], + } + ) support_filters = { 'x': { 'upperLine': {'intercept': 2, 'angle': 5}, @@ -1288,11 +1298,13 @@ class TestApplySupportFilters: def test_two_variables_ands_masks(self): """Two variables apply AND of both masks.""" - data = pd.DataFrame({ - 'a': [1.0, 2.0, 3.0], - 'b': [1.0, 2.0, 3.0], - 'target': [2.0, 2.0, 2.0], - }) + data = pd.DataFrame( + { + 'a': [1.0, 2.0, 3.0], + 'b': [1.0, 2.0, 3.0], + 'target': [2.0, 2.0, 2.0], + } + ) support_filters = { 'a': { 'upper_line': {'intercept': 10, 'angle': 45},