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.
This commit is contained in:
@@ -56,7 +56,7 @@ class ModelServing:
|
|||||||
# Function to list runs for a given experiment
|
# Function to list runs for a given experiment
|
||||||
def search_runs_by_name(
|
def search_runs_by_name(
|
||||||
self, experiment_names: list[str], order_by: None | list[str] = None
|
self, experiment_names: list[str], order_by: None | list[str] = None
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame | list:
|
||||||
"""
|
"""
|
||||||
List runs for a specified MLflow experiment.
|
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.
|
experiment_names (list[str]): List with experiment_names to retrieve runs from.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
pandas.DataFrame: A DataFrame containing run information.
|
Union[pd.DataFrame, list]: A DataFrame or list containing run information.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SientiaMlException: If unable to search runs.
|
SientiaMlException: If unable to search runs.
|
||||||
@@ -73,7 +73,7 @@ class ModelServing:
|
|||||||
runs = mlflow.search_runs(experiment_names=experiment_names, order_by=order_by)
|
runs = mlflow.search_runs(experiment_names=experiment_names, order_by=order_by)
|
||||||
except SientiaMlException as e:
|
except SientiaMlException as e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
raise SientiaMlException from e
|
raise SientiaMlException(str(e)) from e
|
||||||
return runs
|
return runs
|
||||||
|
|
||||||
def set_experiment(self, experiment_identifier: str) -> None:
|
def set_experiment(self, experiment_identifier: str) -> None:
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ def validate_frontend_date_format(fmt: str | None) -> None:
|
|||||||
return
|
return
|
||||||
if fmt not in ALLOWED_FRONTEND_DATE_FORMATS:
|
if fmt not in ALLOWED_FRONTEND_DATE_FORMATS:
|
||||||
allowed = ', '.join(sorted(ALLOWED_FRONTEND_DATE_FORMATS))
|
allowed = ', '.join(sorted(ALLOWED_FRONTEND_DATE_FORMATS))
|
||||||
raise ValueError(
|
raise ValueError(f'Invalid date_format "{fmt}". Allowed formats: {allowed}')
|
||||||
f'Invalid date_format "{fmt}". Allowed formats: {allowed}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _frontend_date_format_to_strftime(fmt: str | None) -> str | None:
|
def _frontend_date_format_to_strftime(fmt: str | None) -> str | None:
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ TEST_SCENARIOS_DIR = PROJECT_ROOT / 'docs' / 'test-scenarios'
|
|||||||
MINIO_ALIAS = 'suse'
|
MINIO_ALIAS = 'suse'
|
||||||
MINIO_BUCKET = 'model-training'
|
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 = {
|
POSTGRES_CONFIG = {
|
||||||
'host': os.getenv('POSTGRES_HOST'),
|
'host': os.getenv('POSTGRES_HOST'),
|
||||||
'port': os.getenv('POSTGRES_PORT'),
|
'port': os.getenv('POSTGRES_PORT'),
|
||||||
@@ -306,6 +312,10 @@ def run_local_pipeline(
|
|||||||
result['error'] = str(exc)
|
result['error'] = str(exc)
|
||||||
return result
|
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)
|
csv_path = _resolve_csv_path(csv_path)
|
||||||
_ensure_source_file(csv_path)
|
_ensure_source_file(csv_path)
|
||||||
payload = _build_local_payload(request_data, 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)
|
result['error'] = str(exc)
|
||||||
return result
|
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)
|
csv_path = _resolve_csv_path(csv_path)
|
||||||
|
|
||||||
# Upload CSV to MinIO
|
# Upload CSV to MinIO
|
||||||
|
|||||||
@@ -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(
|
def test_search_runs_by_name_raises_exception(
|
||||||
mock_logging_error, mock_search_runs, mock_set_tracking_uri
|
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')
|
model_serving = ModelServing(tracking_uri='http://mlflow.example.com')
|
||||||
|
|
||||||
exception = SientiaMlException(message='Search failed')
|
exception = SientiaMlException(message='Search failed')
|
||||||
mock_search_runs.side_effect = exception
|
mock_search_runs.side_effect = exception
|
||||||
|
|
||||||
# The code has a bug on line 80: "raise SientiaMlException from e"
|
with raises(SientiaMlException, match='Search failed'):
|
||||||
# This raises TypeError because SientiaMlException requires 'message' argument
|
|
||||||
with raises(TypeError, match="missing 1 required positional argument: 'message'"):
|
|
||||||
model_serving.search_runs_by_name(['experiment1'])
|
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')
|
@patch('model_manager.sientia.model_serving.mlflow.set_tracking_uri')
|
||||||
|
|||||||
@@ -993,7 +993,9 @@ class TestConfigureDatetimeIndex:
|
|||||||
assert 'var2' in result.columns
|
assert 'var2' in result.columns
|
||||||
|
|
||||||
@pytest.mark.filterwarnings('ignore::UserWarning')
|
@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."""
|
"""Test _configure_datetime_index with invalid timestamp values."""
|
||||||
data = pd.DataFrame(
|
data = pd.DataFrame(
|
||||||
{
|
{
|
||||||
@@ -1199,10 +1201,12 @@ class TestEnsureDateColumnParsed:
|
|||||||
|
|
||||||
def test_parses_column_with_format(self, date_params):
|
def test_parses_column_with_format(self, date_params):
|
||||||
"""When date_column and date_format set, column is parsed as datetime."""
|
"""When date_column and date_format set, column is parsed as datetime."""
|
||||||
data = pd.DataFrame({
|
data = pd.DataFrame(
|
||||||
'ts': ['2023-01-01 10:00:00', '2023-06-15 14:30:00'],
|
{
|
||||||
'x': [1, 2],
|
'ts': ['2023-01-01 10:00:00', '2023-06-15 14:30:00'],
|
||||||
})
|
'x': [1, 2],
|
||||||
|
}
|
||||||
|
)
|
||||||
result = _ensure_date_column_parsed(data, date_params)
|
result = _ensure_date_column_parsed(data, date_params)
|
||||||
assert result['ts'].dtype == 'datetime64[ns]'
|
assert result['ts'].dtype == 'datetime64[ns]'
|
||||||
assert result['ts'].iloc[0].year == 2023
|
assert result['ts'].iloc[0].year == 2023
|
||||||
@@ -1212,10 +1216,12 @@ class TestEnsureDateColumnParsed:
|
|||||||
def test_invalid_values_coerced_to_nat(self, date_params):
|
def test_invalid_values_coerced_to_nat(self, date_params):
|
||||||
"""Invalid date strings are coerced to NaT when format is set."""
|
"""Invalid date strings are coerced to NaT when format is set."""
|
||||||
date_params.date_format = 'yyyy-MM-dd HH:mm:ss'
|
date_params.date_format = 'yyyy-MM-dd HH:mm:ss'
|
||||||
data = pd.DataFrame({
|
data = pd.DataFrame(
|
||||||
'ts': ['2023-01-01 00:00:00', 'not-a-date', '2023-12-31 00:00:00'],
|
{
|
||||||
'x': [1, 2, 3],
|
'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)
|
result = _ensure_date_column_parsed(data, date_params)
|
||||||
assert pd.isna(result['ts'].iloc[1])
|
assert pd.isna(result['ts'].iloc[1])
|
||||||
assert result['ts'].iloc[0].year == 2023
|
assert result['ts'].iloc[0].year == 2023
|
||||||
@@ -1256,10 +1262,12 @@ class TestApplySupportFilters:
|
|||||||
|
|
||||||
def test_snake_case_upper_lower_line(self):
|
def test_snake_case_upper_lower_line(self):
|
||||||
"""Support filters with upper_line/lower_line (snake_case) filter rows."""
|
"""Support filters with upper_line/lower_line (snake_case) filter rows."""
|
||||||
data = pd.DataFrame({
|
data = pd.DataFrame(
|
||||||
'x': [1.0, 2.0, 3.0, 4.0],
|
{
|
||||||
'target': [2.0, 4.0, 6.0, 8.0],
|
'x': [1.0, 2.0, 3.0, 4.0],
|
||||||
})
|
'target': [2.0, 4.0, 6.0, 8.0],
|
||||||
|
}
|
||||||
|
)
|
||||||
support_filters = {
|
support_filters = {
|
||||||
'x': {
|
'x': {
|
||||||
'upper_line': {'intercept': 1.0, 'angle': 50},
|
'upper_line': {'intercept': 1.0, 'angle': 50},
|
||||||
@@ -1272,10 +1280,12 @@ class TestApplySupportFilters:
|
|||||||
|
|
||||||
def test_camel_case_upper_lower_line(self):
|
def test_camel_case_upper_lower_line(self):
|
||||||
"""Support filters with upperLine/lowerLine (camelCase) are accepted."""
|
"""Support filters with upperLine/lowerLine (camelCase) are accepted."""
|
||||||
data = pd.DataFrame({
|
data = pd.DataFrame(
|
||||||
'x': [1.0, 2.0, 3.0],
|
{
|
||||||
'target': [1.0, 2.0, 3.0],
|
'x': [1.0, 2.0, 3.0],
|
||||||
})
|
'target': [1.0, 2.0, 3.0],
|
||||||
|
}
|
||||||
|
)
|
||||||
support_filters = {
|
support_filters = {
|
||||||
'x': {
|
'x': {
|
||||||
'upperLine': {'intercept': 2, 'angle': 5},
|
'upperLine': {'intercept': 2, 'angle': 5},
|
||||||
@@ -1288,11 +1298,13 @@ class TestApplySupportFilters:
|
|||||||
|
|
||||||
def test_two_variables_ands_masks(self):
|
def test_two_variables_ands_masks(self):
|
||||||
"""Two variables apply AND of both masks."""
|
"""Two variables apply AND of both masks."""
|
||||||
data = pd.DataFrame({
|
data = pd.DataFrame(
|
||||||
'a': [1.0, 2.0, 3.0],
|
{
|
||||||
'b': [1.0, 2.0, 3.0],
|
'a': [1.0, 2.0, 3.0],
|
||||||
'target': [2.0, 2.0, 2.0],
|
'b': [1.0, 2.0, 3.0],
|
||||||
})
|
'target': [2.0, 2.0, 2.0],
|
||||||
|
}
|
||||||
|
)
|
||||||
support_filters = {
|
support_filters = {
|
||||||
'a': {
|
'a': {
|
||||||
'upper_line': {'intercept': 10, 'angle': 45},
|
'upper_line': {'intercept': 10, 'angle': 45},
|
||||||
|
|||||||
Reference in New Issue
Block a user