Skip to content

Commit

Permalink
Refactor save paths for plotting summaries
Browse files Browse the repository at this point in the history
  • Loading branch information
CBROWN-ONS committed Sep 12, 2023
1 parent b649429 commit 9d0ca06
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 102 deletions.
116 changes: 62 additions & 54 deletions src/transport_performance/gtfs/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from pretty_html_table import build_table
import zipfile
import pathlib
import warnings
from typing import Union
from plotly.graph_objects import Figure as PlotlyFigure

Expand Down Expand Up @@ -671,9 +670,10 @@ def _plot_summary(
return_html: bool = False,
save_html: bool = False,
save_image: bool = False,
save_pth: Union[pathlib.Path, str] = pathlib.Path(
os.path.join("outputs", "gtfs", "summary_plot")
out_dir: Union[pathlib.Path, str] = pathlib.Path(
os.path.join("outputs", "gtfs")
),
img_type: str = "png",
) -> Union[PlotlyFigure, str]:
"""Plot (and save) a summary table using plotly.
Expand Down Expand Up @@ -715,11 +715,15 @@ def _plot_summary(
save_image : bool, optional
Whether or not to save the plot as a PNG,
by default False
save_pth : Union[pathlib.Path, str], optional
The filepath to save the plot to. Including a file extension is
optional (e.g., .html). File paths must include forward slashes if
they aren't passed as a path object (pathlib.Path).
by default pathlib.Path(os.path.join('outputs', 'gtfs'))
out_dir : Union[pathlib.Path, str], optional
The directory to save the plot into. If a file extension is added
to this directory, it won't be cleaned. Whatever is passed as the
out dir will be used as the parent directory of the save, leaving
the responsibility on the user to specify the correct path.,
by default os.path.join("outputs", "gtfs")
img_type : str, optional
The type of the image to be saved. E.g, .svg or .jpeg.,
by defauly "png"
Returns
-------
Expand All @@ -730,6 +734,8 @@ def _plot_summary(
------
ValueError
An error is raised if orientation is not 'v' or 'h'.
ValueError
An error is raised if an invalid iamge type is passed.
"""
# parameter type defences
Expand All @@ -744,7 +750,13 @@ def _plot_summary(
_type_defence(ylabel, "ylabel", (str, type(None)))
_type_defence(save_html, "save_html", bool)
_type_defence(save_image, "save_iamge", bool)
_check_parent_dir_exists(save_pth, "save_pth", create=True)
_type_defence(img_type, "img_type", str)

raw_pth = os.path.join(
out_dir,
"summary_" + datetime.datetime.now().strftime("%d_%m_%Y-%H_%M_%S"),
)
_check_parent_dir_exists(raw_pth, "save_pth", create=True)

# orientation input defences
if orientation.lower() not in ["v", "h"]:
Expand Down Expand Up @@ -825,48 +837,34 @@ def _plot_summary(
if plotly_kwargs:
fig.update_layout(**plotly_kwargs)

# break up the save path
main, ext = os.path.splitext(save_pth)

# save the plot if specified (with correct file type)
if save_html:
if ext.lower() != ".html":
warnings.warn(
(
"HTML save requested but accepted type "
"not specified.\n"
"Accepted image formats include ['.html']\n"
"Saving as .html"
),
UserWarning,
)
plotly_io.write_html(
fig=fig,
file=os.path.normpath(main + ".html"),
file=os.path.normpath(raw_pth + ".html"),
full_html=False,
)

if save_image:
valid_img_formats = [
".png",
".pdf",
"png",
"pdf",
"jpg",
"jpeg",
".webp",
".svg",
"webp",
"svg",
]
if ext.lower() not in valid_img_formats:
warnings.warn(
(
"Image save requested but accepted type not "
"specified.\n"
f"Accepted image formats include {valid_img_formats}\n"
"Saving as .png"
),
UserWarning,
if img_type.lower().replace(".", "") not in valid_img_formats:
raise ValueError(
"Please specify a valid image format. Valid formats "
f"include {valid_img_formats}"
)
ext = ".png"
plotly_io.write_image(fig=fig, file=os.path.normpath(main + ext))
plotly_io.write_image(
fig=fig,
file=os.path.normpath(
raw_pth + f".{img_type.replace('.', '')}"
),
)
if return_html:
return plotly_io.to_html(fig, full_html=False)
return fig
Expand All @@ -883,9 +881,10 @@ def plot_route_summary(
ylabel: str = None,
save_html: bool = False,
save_image: bool = False,
save_pth: Union[pathlib.Path, str] = pathlib.Path(
out_dir: Union[pathlib.Path, str] = pathlib.Path(
os.path.join("outputs", "gtfs")
),
img_type: str = "png",
) -> Union[PlotlyFigure, str]:
"""Plot the summarised route data of a GTFS file.
Expand Down Expand Up @@ -924,11 +923,15 @@ def plot_route_summary(
save_image : bool, optional
Whether or not to save the plot as a PNG,
by default False
save_pth : Union[pathlib.Path, str], optional
The filepath to save the plot to. Including a file extension is
optional (e.g., .html). File paths must include forward slashes if
they aren't passed as a path object (pathlib.Path).
by default pathlib.Path(os.path.join('outputs', 'gtfs'))
out_dir : Union[pathlib.Path, str], optional
The directory to save the plot into. If a file extension is added
to this directory, it won't be cleaned. Whatever is passed as the
out dir will be used as the parent directory of the save, leaving
the responsibility on the user to specify the correct path.,
by default os.path.join("outputs", "gtfs")
img_type : str, optional
The type of the image to be saved. E.g, .svg or .jpeg.,
by defauly "png"
Returns
-------
Expand Down Expand Up @@ -963,8 +966,9 @@ def plot_route_summary(
xlabel=xlabel,
ylabel=ylabel,
save_html=save_html,
save_pth=save_pth,
out_dir=out_dir,
save_image=save_image,
img_type=img_type,
)
return plot

Expand All @@ -979,10 +983,10 @@ def plot_trip_summary(
xlabel: str = None,
ylabel: str = None,
save_html: bool = False,
save_image: bool = False,
save_pth: Union[pathlib.Path, str] = pathlib.Path(
out_dir: Union[pathlib.Path, str] = pathlib.Path(
os.path.join("outputs", "gtfs")
),
img_type: str = "png",
):
"""Plot the summarised trip data of a GTFS file.
Expand Down Expand Up @@ -1021,11 +1025,15 @@ def plot_trip_summary(
save_image : bool, optional
Whether or not to save the plot as a PNG,
by default False
save_pth : Union[pathlib.Path, str], optional
The filepath to save the plot to. Including a file extension is
optional (e.g., .html). File paths must include forward slashes if
they aren't passed as a path object (pathlib.Path).
by default pathlib.Path(os.path.join('outputs', 'gtfs'))
out_dir : Union[pathlib.Path, str], optional
The directory to save the plot into. If a file extension is added
to this directory, it won't be cleaned. Whatever is passed as the
out dir will be used as the parent directory of the save, leaving
the responsibility on the user to specify the correct path.,
by default os.path.join("outputs", "gtfs")
img_type : str, optional
The type of the image to be saved. E.g, .svg or .jpeg.,
by defauly "png"
Returns
-------
Expand Down Expand Up @@ -1060,8 +1068,8 @@ def plot_trip_summary(
xlabel=xlabel,
ylabel=ylabel,
save_html=save_html,
save_pth=save_pth,
save_image=save_image,
out_dir=out_dir,
img_type=img_type,
)
return plot

Expand Down
66 changes: 18 additions & 48 deletions tests/gtfs/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,75 +653,45 @@ def test__plot_summary_on_pass(self, gtfs_fixture, tmp_path):
height=800,
save_html=True,
save_image=True,
save_pth=pathlib.Path(
os.path.join(tmp_path, "save_test", "test_image")
),
ylabel="Mean",
xlabel="Day",
orientation="h",
plotly_kwargs={"legend": dict(bgcolor="lightgrey")},
out_dir=os.path.join(tmp_path, "save_test"),
)

# general save test
save_dir = os.listdir(os.path.join(tmp_path, "save_test"))
counts = {"html": 0, "png": 0}
for pth in save_dir:
if ".html" in pth:
counts["html"] += 1
elif ".png" in pth:
counts["png"] += 1

assert os.path.exists(
os.path.join(tmp_path, "save_test")
), "'save_test' dir could not be created'"
assert os.path.exists(
os.path.join(tmp_path, "save_test", "test_image.html")
), "Failed to save summary in HTML"
assert os.path.exists(
os.path.join(tmp_path, "save_test", "test_image.png")
), "Failed to save summary as a PNG"

# save test for HTML with an invalid file extension
with pytest.warns(
UserWarning,
match=re.escape(
"HTML save requested but accepted type not specified.\n"
"Accepted image formats include ['.html']\n"
"Saving as .html"
),
):
gtfs_fixture._plot_summary(
gtfs_fixture.daily_route_summary,
"route_count_mean",
save_html=True,
save_pth=pathlib.Path(
os.path.join(
tmp_path, "save_test", "invalid_html_ext.nothtml"
)
),
)

assert os.path.exists(
os.path.join(tmp_path, "save_test", "invalid_html_ext.html")
), "Failed to save HTML with automatic file extensions"
assert counts["html"] == 1, "Failed to save plot as HTML"
assert counts["png"] == 1, "Failed to save plot as png"

# save test for an image with invalid file extension
valid_img_formats = [".png", ".pdf", "jpg", "jpeg", ".webp", ".svg"]
with pytest.warns(
UserWarning,
valid_img_formats = ["png", "pdf", "jpg", "jpeg", "webp", "svg"]
with pytest.raises(
ValueError,
match=re.escape(
"Image save requested but accepted type not specified.\n"
f"Accepted image formats include {valid_img_formats}\n"
"Saving as .png"
"Please specify a valid image format. Valid formats "
f"include {valid_img_formats}"
),
):
gtfs_fixture._plot_summary(
gtfs_fixture.daily_route_summary,
"route_count_mean",
save_image=True,
save_pth=pathlib.Path(
os.path.join(
tmp_path, "save_test", "invalid_image_ext.notimg"
)
),
out_dir=os.path.join(tmp_path, "outputs"),
img_type="test",
)

assert os.path.exists(
os.path.join(tmp_path, "save_test", "invalid_image_ext.png")
), "Failed to save HTML with automatic file extensions"

def test__plot_route_summary_defences(self, gtfs_fixture):
"""Test the defences for the small wrapper plot_route_summary()."""
# test attribute checks
Expand Down

0 comments on commit 9d0ca06

Please sign in to comment.