Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ After completing a milestone, create a pull request with your changes for review
- [x] Integrated evaluation metrics and plots into the Data Explorer page
- [x] Implemented modeling page with model selection, training, cross-validation, and export functionality
- [x] Added histogram, box plot, violin plot, and heatmap UI with export options
- [x] Added logging utilities and integrated logging across the app

## PR17: Robust Error Handling

Expand Down
3 changes: 3 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import streamlit as st
from utils import ui
from utils.logging import configure_logging

configure_logging()

st.set_page_config(page_title="PredictStream", layout="wide")

Expand Down
7 changes: 7 additions & 0 deletions pages/data_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
from utils import eda
from utils import ui
from utils import components
import logging
from utils.logging import configure_logging

configure_logging()



Expand Down Expand Up @@ -47,6 +51,9 @@ def main() -> None:
)
st.success(f"{name} loaded!")
except (ValueError, TypeError) as exc:
logging.getLogger(__name__).error(
"Failed to load sample data %s: %s", name, exc
)
st.error(f"Failed to load sample data: {exc}")

with st.expander("Help"):
Expand Down
3 changes: 3 additions & 0 deletions pages/modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import streamlit as st

from utils import model, predict, ui
from utils.logging import configure_logging

configure_logging()

st.set_page_config(page_title="Modeling", layout="wide")

Expand Down
3 changes: 3 additions & 0 deletions pages/prediction.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from utils import data as data_utils
from utils import predict
from utils import ui
from utils.logging import configure_logging

configure_logging()

st.set_page_config(page_title="Prediction", layout="wide")

Expand Down
3 changes: 3 additions & 0 deletions pages/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from utils import data as data_utils
from utils import eda
from utils import ui
from utils.logging import configure_logging

configure_logging()

st.set_page_config(page_title="Report", layout="wide")

Expand Down
3 changes: 3 additions & 0 deletions pages/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import streamlit as st

from utils import ui, viz
from utils.logging import configure_logging

configure_logging()

st.set_page_config(page_title="Time Series", layout="wide")

Expand Down
10 changes: 10 additions & 0 deletions tests/test_logging_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import logging
from importlib import reload
from utils import logging as log_utils


def test_configure_logging_sets_level():
reload(log_utils)
log_utils.configure_logging(level=logging.INFO)
assert logging.getLogger().level == logging.INFO

2 changes: 2 additions & 0 deletions utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from . import predict
from . import ui
from . import transform
from . import logging

__all__ = [
"config",
Expand All @@ -22,4 +23,5 @@
"predict",
"ui",
"transform",
"logging",
]
7 changes: 7 additions & 0 deletions utils/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pathlib import Path
import tempfile
import logging

import pandas as pd
import streamlit as st
Expand Down Expand Up @@ -441,6 +442,9 @@ def classification_training_section(data: pd.DataFrame) -> None:
)
except Exception as exc: # pragma: no cover - training can fail for many reasons
progress.progress(0)
logging.getLogger(__name__).exception(
"Classification training failed: %s", exc
)
st.error(f"Classification training failed: {exc}")

st.subheader("Detected Problem Type")
Expand Down Expand Up @@ -637,6 +641,9 @@ def regression_training_section(data: pd.DataFrame) -> None:
)
except Exception as exc: # pragma: no cover - training can fail for many reasons
progress_r.progress(0)
logging.getLogger(__name__).exception(
"Regression training failed: %s", exc
)
st.error(f"Regression training failed: {exc}")

if st.button("Compare Regression Models") and feature_cols_r:
Expand Down
10 changes: 9 additions & 1 deletion utils/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pathlib import Path
from typing import Any, Iterable
import logging

MAX_UPLOAD_SIZE_MB = 100

Expand Down Expand Up @@ -37,7 +38,8 @@ def validate_file_size(file: Any, max_mb: int = MAX_UPLOAD_SIZE_MB) -> int:
else:
path = Path(getattr(file, "name"))
size = path.stat().st_size
except OSError:
except OSError as exc:
logging.getLogger(__name__).warning("Could not determine file size: %s", exc)
size = None
if size is not None and size > limit:
raise ValueError(f"File size {size} exceeds limit of {limit} bytes")
Expand All @@ -60,6 +62,7 @@ def load_data(file: Any) -> pd.DataFrame:
path = Path(str(file))
ext = path.suffix.lower()
if not path.exists():
logging.getLogger(__name__).error("Invalid file path: %s", path)
raise ValueError("Invalid file path")

try:
Expand All @@ -68,7 +71,9 @@ def load_data(file: Any) -> pd.DataFrame:
if ext in {".xls", ".xlsx"}:
return pd.read_excel(file)
except Exception as exc: # pragma: no cover - pass through
logging.getLogger(__name__).exception("Failed to read file: %s", exc)
raise ValueError(f"Failed to read file: {exc}") from exc
logging.getLogger(__name__).error("Unsupported file type: %s", ext)
raise ValueError(f"Unsupported file type: {ext}")


Expand Down Expand Up @@ -104,9 +109,11 @@ def validate_file_type(file: Any, allowed_types: Iterable[str]) -> str:
path = Path(str(file))
ext = path.suffix.lower()
if not path.exists():
logging.getLogger(__name__).error("Invalid file path: %s", path)
raise ValueError("Invalid file path")

if ext not in {f".{t.lstrip('.').lower()}" for t in allowed_types}:
logging.getLogger(__name__).error("Unsupported file type: %s", ext)
raise ValueError(f"Unsupported file type: {ext}")
return ext

Expand All @@ -128,6 +135,7 @@ def process_uploaded_file(
df = load_data(uploaded_file)
df = convert_dtypes(df)
except (ValueError, TypeError) as exc: # pragma: no cover - tested via wrapper
logging.getLogger(__name__).error("Failed to load uploaded file: %s", exc)
st.error(f"Failed to load file: {exc}")
return None
st.session_state[session_key] = df
Expand Down
14 changes: 14 additions & 0 deletions utils/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import logging


def configure_logging(level: int = logging.INFO) -> None:
"""Configure root logger if not already configured."""
root = logging.getLogger()
if not root.handlers:
logging.basicConfig(
level=level,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
else:
root.setLevel(level)

7 changes: 7 additions & 0 deletions utils/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pathlib import Path
from typing import Optional
import logging

import pandas as pd
import plotly.express as px
Expand Down Expand Up @@ -135,20 +136,26 @@ def export_figure(fig: object, path: Path) -> None:
if isinstance(fig, go.Figure):
fig.write_html(str(path))
else:
logging.getLogger(__name__).error("HTML export requires a Plotly figure")
raise ValueError("HTML export requires a Plotly figure")
elif ext in {".png", ".jpg", ".jpeg"}:
if isinstance(fig, go.Figure):
try:
fig.write_image(str(path))
except ValueError as exc:
logging.getLogger(__name__).exception(
"Static image export failed: %s", exc
)
raise RuntimeError(
"Static image export requires the kaleido package"
) from exc
elif isinstance(fig, plt.Figure):
fig.savefig(path)
else:
logging.getLogger(__name__).error("Unsupported figure type for image export")
raise ValueError("Unsupported figure type for image export")
else:
logging.getLogger(__name__).error("Unsupported export format: %s", ext)
raise ValueError(f"Unsupported export format: {ext}")


Expand Down