diff --git a/.github/workflows/all-os-tests.yml b/.github/workflows/all-os-tests.yml new file mode 100644 index 00000000..7c44adc0 --- /dev/null +++ b/.github/workflows/all-os-tests.yml @@ -0,0 +1,34 @@ +# This workflow will install OS dependencies and run a 'base' set of unit tests with Python 3.9 +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Unit tests on macOS/Linux/Windows + +on: + push: + pull_request: + branches: [ "dev", "main" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ["3.9"] + os: ["macos-latest", "ubuntu-latest", "windows-latest"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run Integration Tests # run only those tests marked runinteg & with no osmosis deps + run: | + pytest -m runinteg --runinteg --deselect tests/osm/ + - name: Test with pytest # run only tests with no osmosis deps + run: | + pytest --deselect tests/osm/ diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 047d1369..9955734d 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,4 +1,4 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# This workflow will install Python dependencies, run tests and lint with Python 3.9 # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Python package @@ -10,7 +10,6 @@ on: jobs: build: - runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -23,11 +22,6 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - - name: Install geos # required for cartopy installation - uses: ConorMacBride/install-package@v1 - with: - brew: geos - apt: libgeos-dev - name: Install dependencies run: | python -m pip install --upgrade pip @@ -40,11 +34,19 @@ jobs: - name: Check Java Install run: | java --version - - name: Install Osmosis - uses: ConorMacBride/install-package@v1 - with: - brew: osmosis - apt: osmosis + - name: Install mac depencies with brew + if: runner.os == 'macOS' # not updating brew version, issue with aom + run: | + brew install geos + brew install osmosis + shell: sh + - name: Install linux depencies with apt + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install -y libgeos-dev + sudo apt install -y osmosis + shell: sh - name: Run Integration Tests run: | pytest -m runinteg --runinteg # run only those tests marked runinteg @@ -56,10 +58,12 @@ jobs: run: | pytest - name: Generate Report + if: runner.os == 'macOS' # run coverage report only on macOS run: | coverage run -m pytest coverage xml - name: Upload coverage reports to Codecov + if: runner.os == 'macOS' # run coverage report only on macOS uses: codecov/codecov-action@v3 with: file: ./coverage.xml diff --git a/notebooks/urban_centres/poc_urban_centres.py b/notebooks/urban_centres/poc_urban_centres.py index b9bd419c..7e340a55 100644 --- a/notebooks/urban_centres/poc_urban_centres.py +++ b/notebooks/urban_centres/poc_urban_centres.py @@ -84,9 +84,13 @@ AREA_OF_INTEREST = "newport" BBOX = BBOX_DICT[AREA_OF_INTEREST] +# bbox bbox_npt = gpd.GeoDataFrame(index=[0], crs="epsg:4326", geometry=[box(*BBOX)]) bbox_npt_r = bbox_npt.to_crs("esri:54009") -coords = (float(bbox_npt.centroid.y), float(bbox_npt.centroid.x)) + +# bbox centroid +bbox_npt_centroid = bbox_npt.to_crs("epsg:27700").centroid.to_crs("epsg:4326") +coords = (bbox_npt_centroid.y[0], bbox_npt_centroid.x[0]) # pop only criteria npt = ucc.UrbanCentre(file=(MERGED_DIR)) @@ -105,9 +109,13 @@ AREA_OF_INTEREST = "leeds" BBOX = BBOX_DICT[AREA_OF_INTEREST] +# bbox bbox_lds = gpd.GeoDataFrame(index=[0], crs="epsg:4326", geometry=[box(*BBOX)]) bbox_lds_r = bbox_lds.to_crs("esri:54009") -coords = (float(bbox_lds.centroid.y), float(bbox_lds.centroid.x)) + +# bbox centroid +bbox_lds_centroid = bbox_lds.to_crs("epsg:27700").centroid.to_crs("epsg:4326") +coords = (bbox_lds_centroid.y[0], bbox_lds_centroid.x[0]) # pop only criteria lds = ucc.UrbanCentre(file=(MERGED_DIR)) @@ -126,9 +134,13 @@ AREA_OF_INTEREST = "london" BBOX = BBOX_DICT[AREA_OF_INTEREST] +# bbox bbox_lnd = gpd.GeoDataFrame(index=[0], crs="epsg:4326", geometry=[box(*BBOX)]) bbox_lnd_r = bbox_lnd.to_crs("esri:54009") -coords = (float(bbox_lnd.centroid.y), float(bbox_lnd.centroid.x)) + +# bbox centroid +bbox_lnd_centroid = bbox_lnd.to_crs("epsg:27700").centroid.to_crs("epsg:4326") +coords = (bbox_lnd_centroid.y[0], bbox_lnd_centroid.x[0]) # pop only criteria lnd = ucc.UrbanCentre(file=(MERGED_DIR)) @@ -146,9 +158,13 @@ AREA_OF_INTEREST = "marseille" BBOX = BBOX_DICT[AREA_OF_INTEREST] +# bbox bbox_mrs = gpd.GeoDataFrame(index=[0], crs="epsg:4326", geometry=[box(*BBOX)]) bbox_mrs_r = bbox_mrs.to_crs("esri:54009") -coords = (float(bbox_mrs.centroid.y), float(bbox_mrs.centroid.x)) + +# bbox centroid +bbox_mrs_centroid = bbox_mrs_r.centroid.to_crs("epsg:4326") +coords = (bbox_mrs_centroid.y[0], bbox_mrs_centroid.x[0]) # pop only criteria mrs = ucc.UrbanCentre(file=(MERGED_DIR)) diff --git a/requirements.txt b/requirements.txt index a19383b0..c6b8de04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ pre-commit r5py>=0.0.4 gtfs_kit==5.2.7 rasterio -matplotlib +matplotlib>=3.7.0 scipy rioxarray geopandas @@ -13,7 +13,7 @@ coverage pyprojroot pytest-lazy-fixture ipykernel==6.23.1 -pandas +pandas<2.1.0 beautifulsoup4 requests pytest-mock diff --git a/src/transport_performance/gtfs/gtfs_utils.py b/src/transport_performance/gtfs/gtfs_utils.py index 2865770e..4703c5f0 100644 --- a/src/transport_performance/gtfs/gtfs_utils.py +++ b/src/transport_performance/gtfs/gtfs_utils.py @@ -9,8 +9,7 @@ from transport_performance.utils.defence import ( _is_expected_filetype, _check_list, - _dataframe_defence, - _bool_defence, + _type_defence, ) @@ -109,8 +108,8 @@ def convert_pandas_to_plotly( } } # defences - _dataframe_defence(df, "df") - _bool_defence(return_html, "return_html") + _type_defence(df, "df", pd.DataFrame) + _type_defence(return_html, "return_html", bool) if scheme not in list(schemes.keys()): raise LookupError( f"{scheme} is not a valid colour scheme." diff --git a/src/transport_performance/gtfs/report/report_utils.py b/src/transport_performance/gtfs/report/report_utils.py index 21536338..ad0f4a9c 100644 --- a/src/transport_performance/gtfs/report/report_utils.py +++ b/src/transport_performance/gtfs/report/report_utils.py @@ -5,9 +5,8 @@ import os from transport_performance.utils.defence import ( - _bool_defence, - _string_defence, - _is_path_like, + _type_defence, + _handle_path_like, _check_parent_dir_exists, ) @@ -76,7 +75,7 @@ def __init__(self, path: Union[str, pathlib.Path]) -> None: None """ - _is_path_like(path, "path") + _handle_path_like(path, "path") with open(path, "r", encoding="utf8") as f: self.template = f.read() return None @@ -109,9 +108,9 @@ def insert( place-holder but 'replace_multiple' is not True """ - _string_defence(placeholder, "placeholder") - _string_defence(value, "value") - _bool_defence(replace_multiple, "replace_multiple") + _type_defence(placeholder, "placeholder", str) + _type_defence(value, "value", str) + _type_defence(replace_multiple, "replace_multiple", bool) occurences = len(self.template.split(f"[{placeholder}]")) - 1 if occurences > 1 and not replace_multiple: raise ValueError( diff --git a/src/transport_performance/gtfs/routes.py b/src/transport_performance/gtfs/routes.py index e989645f..d0003f57 100644 --- a/src/transport_performance/gtfs/routes.py +++ b/src/transport_performance/gtfs/routes.py @@ -4,7 +4,7 @@ import requests import warnings -from transport_performance.utils.defence import _url_defence, _bool_defence +from transport_performance.utils.defence import _url_defence, _type_defence warnings.filterwarnings( action="ignore", category=DeprecationWarning, module=".*pkg_resources" @@ -98,7 +98,7 @@ def scrape_route_type_lookup( for url in [gtfs_url, ext_spec_url]: _url_defence(url) - _bool_defence(extended_schema, "extended_schema") + _type_defence(extended_schema, "extended_schema", bool) # Get the basic scheme lookup resp_txt = _get_response_text(gtfs_url) soup = BeautifulSoup(resp_txt, "html.parser") diff --git a/src/transport_performance/gtfs/validation.py b/src/transport_performance/gtfs/validation.py index 7be31483..38c77b95 100644 --- a/src/transport_performance/gtfs/validation.py +++ b/src/transport_performance/gtfs/validation.py @@ -23,12 +23,7 @@ _check_namespace_export, _check_parent_dir_exists, _check_column_in_df, - _bool_defence, - _integer_defence, - _string_defence, - _dataframe_defence, - _dict_defence, - _string_and_nonetype_defence, + _type_defence, ) from transport_performance.gtfs.report.report_utils import ( @@ -738,17 +733,17 @@ def _plot_summary( """ # parameter type defences - _dataframe_defence(summary_df, "summary_df") - _string_defence(day_column, "day_column") - _string_defence(target_column, "target_column") - _dict_defence(plotly_kwargs, "plotly_kwargs") - _bool_defence(return_html, "return_html") - _integer_defence(width, "width") - _integer_defence(height, "height") - _string_and_nonetype_defence(xlabel, "xlabel") - _string_and_nonetype_defence(ylabel, "ylabel") - _bool_defence(save_html, "save_html") - _bool_defence(save_image, "save_iamge") + _type_defence(summary_df, "summary_df", pd.DataFrame) + _type_defence(day_column, "day_column", str) + _type_defence(target_column, "target_column", str) + _type_defence(plotly_kwargs, "plotly_kwargs", dict) + _type_defence(return_html, "return_html", bool) + _type_defence(width, "width", int) + _type_defence(height, "height", int) + _type_defence(xlabel, "xlabel", (str, type(None))) + _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) # orientation input defences @@ -1269,8 +1264,8 @@ def html_report( An error raised if the type of summary passed is invalid """ - _bool_defence(overwrite, "overwrite") - _string_defence(summary_type, "summary_type") + _type_defence(overwrite, "overwrite", bool) + _type_defence(summary_type, "summary_type", str) set_up_report_dir(path=report_dir, overwrite=overwrite) summary_type = summary_type.lower() if summary_type not in ["mean", "min", "max", "median"]: diff --git a/src/transport_performance/osm/osm_utils.py b/src/transport_performance/osm/osm_utils.py index e4501416..d086eb6a 100644 --- a/src/transport_performance/osm/osm_utils.py +++ b/src/transport_performance/osm/osm_utils.py @@ -3,7 +3,7 @@ from pyprojroot import here from transport_performance.utils.defence import ( - _bool_defence, + _type_defence, _check_list, _check_parent_dir_exists, _is_expected_filetype, @@ -54,7 +54,7 @@ def filter_osm( "tag_filter": tag_filter, "install_osmosis": install_osmosis, }.items(): - _bool_defence(val, param_nm=nm) + _type_defence(val, nm, bool) # check bbox values makes sense, else osmosis will error if not bbox[0] < bbox[2]: raise ValueError( diff --git a/src/transport_performance/population/rasterpop.py b/src/transport_performance/population/rasterpop.py index be7de416..934793a2 100644 --- a/src/transport_performance/population/rasterpop.py +++ b/src/transport_performance/population/rasterpop.py @@ -34,7 +34,7 @@ class RasterPop: pop_gdf : gpd.GeoDataFrame A geopandas dataframe of the input data, with gridded geometry. This is in the same CRS as the input raster data. - centroid_gdf + centroid_gdf : gpd.GeoDataFrame A geopandas dataframe of grid centroids, converted to EPSG:4326 for transport analysis. @@ -153,7 +153,7 @@ def plot( save : str, optional Filepath to save file, with the file extension, by default None meaning a file will not be saved. - **kwargs + kwargs : dict, optional Extra arguments passed to plotting functions to configure the plot styling. See Notes for more support. @@ -177,9 +177,10 @@ def plot( ----- Calling `help` as follows will provide more insights on possible kwarg arguments for the valid plotting backends: - - Folium backend: `help(RasterPop._plot_folium) - - Matplotlib backend: `help(RasterPop._plot_matplotlib) - - Cartopy backend: `help(RasterPop._plot_cartopy) + + - Folium backend: `help(RasterPop._plot_folium)` + - Matplotlib backend: `help(RasterPop._plot_matplotlib)` + - Cartopy backend: `help(RasterPop._plot_cartopy)` """ # record of valid which values @@ -463,18 +464,30 @@ def _plot_folium( ) # add the centroids to a separate layer - self.centroid_gdf.explore( - self.__UC_COL_NAME, - name="Centroids", - m=m, - show=False, - style_kwds={ + # conditionally style plot based on whether UC is provided + if self._uc_gdf is not None: + centroid_plot_col = self.__UC_COL_NAME + # this dict will change the centriod color in/out the UC. + centroid_style_dict = { "style_function": lambda x: { "color": "#BC544B" if x["properties"][self.__UC_COL_NAME] is False else "#8B0000" } - }, + } + else: + centroid_plot_col = None + centroid_style_dict = { + "style_function": lambda x: {"color": "#BC544B"} + } + + # add in the centroid layer with the conditional styling + self.centroid_gdf.explore( + centroid_plot_col, + name="Centroids", + m=m, + show=False, + style_kwds=centroid_style_dict, legend=False, ) diff --git a/src/transport_performance/urban_centres/raster_uc.py b/src/transport_performance/urban_centres/raster_uc.py index 95d4c58b..d6fd4067 100644 --- a/src/transport_performance/urban_centres/raster_uc.py +++ b/src/transport_performance/urban_centres/raster_uc.py @@ -14,12 +14,16 @@ from rasterio.mask import raster_geometry_mask from rasterio.transform import rowcol from scipy.ndimage import generic_filter, label +from transport_performance.utils.defence import _handle_path_like class UrbanCentre: """Create urban centre object.""" def __init__(self, file): + + # check that path is str or PosixPath + file = _handle_path_like(file, "file") self.file = file def get_urban_centre( @@ -126,10 +130,6 @@ def _window_raster( tuple[2]: crs string from the raster. """ - if not isinstance(file, str): - raise TypeError( - "`file` expected string, " f"got {type(file).__name__}." - ) if not isinstance(bbox, gpd.GeoDataFrame): raise TypeError( "`bbox` expected GeoDataFrame, " f"got {type(bbox).__name__}." @@ -282,7 +282,6 @@ def _check_cluster_pop( urban_centres = labelled_array.copy() for n in range(1, num_clusters + 1): total_pop = ma.sum(ma.masked_where(urban_centres != n, band)) - # print(n, total_pop) if total_pop < cluster_pop_threshold: urban_centres[urban_centres == n] = 0 @@ -341,7 +340,7 @@ def _fill_gaps( threshold: int If the number of cells adjacent to any empty cell belonging to a cluster is higher than the threshold, the cell is filled with - the cluster value. + the cluster value. Needs to be between 5 and 8. Returns ------- @@ -358,10 +357,10 @@ def _fill_gaps( "`threshold` expected integer, " f"got {type(threshold).__name__}" ) - if not (1 <= threshold <= 9): + if not (5 <= threshold <= 8): raise ValueError( "Wrong value for `threshold`, " - "please enter value between 1 and 9" + "please enter value between 5 and 8" ) filled = urban_centres.copy() @@ -377,7 +376,6 @@ def _fill_gaps( extra_keywords={"threshold": threshold}, ) if np.array_equal(filled, check): - # print("iter", n) break return filled diff --git a/src/transport_performance/utils/defence.py b/src/transport_performance/utils/defence.py index 06442b0a..a56e92a9 100644 --- a/src/transport_performance/utils/defence.py +++ b/src/transport_performance/utils/defence.py @@ -6,11 +6,11 @@ import pandas as pd -def _is_path_like(pth, param_nm): +def _handle_path_like(pth, param_nm): """Handle path-like parameter values. - It is important to note that paths including backslashes are not accepted, - with forward slashes being the only option. + Checks a path for symlinks and relative paths. Converts to realpath & + outputs pathlib.Path object (platform agnostic). Parameters ---------- @@ -22,23 +22,25 @@ def _is_path_like(pth, param_nm): Raises ------ - TypeError: `pth` is not either of string or pathlib.PosixPath. + TypeError: `pth` is not either of string or pathlib.Path. Returns ------- - None + pathlib.Path + Platform agnostic representation of pth. """ if not isinstance(pth, (str, pathlib.Path)): raise TypeError(f"`{param_nm}` expected path-like, found {type(pth)}.") + # ensure returned path is not relative or contains symbolic links + pth = os.path.realpath(pth) + if not isinstance(pth, pathlib.Path): - if "\\" in repr(pth): - raise ValueError( - "Please specify string paths with single forward" - " slashes only." - f" Got {repr(pth)}" - ) + # coerce to Path even if user passes string + pth = pathlib.Path(pth) + + return pth def _check_parent_dir_exists( @@ -74,11 +76,7 @@ def _check_parent_dir_exists( the create parameter is False. """ - _is_path_like(pth, param_nm) - # convert path to the correct OS specific format - pth = pathlib.Path(pth) - # realpath helps to catch cases where relative paths are passed in main - pth = os.path.realpath(pth) + pth = _handle_path_like(pth, param_nm) parent = os.path.dirname(pth) if not os.path.exists(parent): if create: @@ -117,7 +115,7 @@ def _is_expected_filetype(pth, param_nm, check_existing=True, exp_ext=".zip"): None """ - _is_path_like(pth=pth, param_nm=param_nm) + pth = _handle_path_like(pth=pth, param_nm=param_nm) _, ext = os.path.splitext(pth) if check_existing and not os.path.exists(pth): @@ -152,68 +150,40 @@ def _check_namespace_export(pkg=np, func=np.min): def _url_defence(url): """Defence checking. Not exported.""" - if not isinstance(url, str): - raise TypeError(f"url {url} expected string, instead got {type(url)}") - elif not url.startswith((r"http://", r"https://")): + _type_defence(url, "url", str) + if not url.startswith((r"http://", r"https://")): raise ValueError(f"url string expected protocol, instead found {url}") return None -def _bool_defence(some_bool, param_nm): - """Defence checking. Not exported.""" - if not isinstance(some_bool, bool): - raise TypeError( - f"`{param_nm}` expected boolean. Got {type(some_bool)}" - ) +def _type_defence(some_object, param_nm, types) -> None: + """Defence checking utility. Can handle NoneType. - return None - - -def _string_defence(string, param_nm): - """Defence checking. Not exported.""" - if not isinstance(string, str): - raise TypeError(f"'{param_nm}' expected str. Got {type(string)}") - - return None - - -def _integer_defence(some_int, param_nm): - """Defence checking. Not exported.""" - if not isinstance(some_int, int): - raise TypeError(f"'{param_nm}' expected int. Got {type(some_int)}") - - return None - - -def _dict_defence(some_dict, param_nm): - """Defence checking. Not exported.""" - if not isinstance(some_dict, dict): - raise TypeError(f"'{param_nm}' expected dict. Got {type(some_dict)}") - - return None + Parameters + ---------- + some_object : Any + Object to test with isinstance. + param_nm : str + A name for the parameter. Useful when this utility is used in a wrapper + to inherit the parent's parameter name and present in error message. + types : type or tuple + A type or a tuple of types to test `some_object` against. + Raises + ------ + TypeError + `some_object` is not of type `types`. -def _dataframe_defence(some_df, param_nm): - """Defence checking. Not exported.""" - if not isinstance(some_df, pd.DataFrame): + """ + if not isinstance(some_object, types): raise TypeError( - f"'{param_nm}' expected pd.DataFrame. Got {type(some_df)}" + f"`{param_nm}` expected {types}. Got {type(some_object)}" ) return None -# main use case for this is to avoid -# complexity limits in -# GtfsInstance._plot_summary() -def _string_and_nonetype_defence(some_value, param_nm): - if not isinstance(some_value, (str, type(None))): - raise TypeError( - f"'{param_nm}' expected type str. Found type {type(some_value)}" - ) - - def _check_list(ls, param_nm, check_elements=True, exp_type=str): """Check a list and its elements for type. diff --git a/tests/gtfs/test_routes.py b/tests/gtfs/test_routes.py index 38eaa23a..efabfc2f 100644 --- a/tests/gtfs/test_routes.py +++ b/tests/gtfs/test_routes.py @@ -47,12 +47,12 @@ def test_defensive_exceptions(self): """Test the defensive checks raise as expected.""" with pytest.raises( TypeError, - match=r"url 1 expected string, instead got ", + match=r"`url` expected . Got ", ): scrape_route_type_lookup(gtfs_url=1) with pytest.raises( TypeError, - match=r"url False expected string, instead got ", + match=r"`url` expected . Got ", ): scrape_route_type_lookup(ext_spec_url=False) with pytest.raises( @@ -62,7 +62,7 @@ def test_defensive_exceptions(self): scrape_route_type_lookup(gtfs_url="foobar") with pytest.raises( TypeError, - match=r"`extended_schema` expected boolean. Got ", + match=r"`extended_schema` .* . Got ", ): scrape_route_type_lookup(extended_schema="True") diff --git a/tests/gtfs/test_validation.py b/tests/gtfs/test_validation.py index 790ef99a..d13cf340 100644 --- a/tests/gtfs/test_validation.py +++ b/tests/gtfs/test_validation.py @@ -37,7 +37,10 @@ def test_init_defensive_behaviours(self): ): GtfsInstance(gtfs_pth=1) with pytest.raises( - FileExistsError, match=r"doesnt/exist not found on file." + # match refactored to work on windows & mac + # see https://regex101.com/r/i1C4I4/1 + FileExistsError, + match=r"doesnt(/|\\)exist not found on file.", ): GtfsInstance(gtfs_pth="doesnt/exist") # a case where file is found but not a zip directory @@ -157,8 +160,9 @@ def test_print_alerts_multi_case(self, mocked_print, gtfs_fixture): ], f"Expected print statements about GTFS warnings. Found: {fun_out}" @patch("builtins.print") - def test_viz_stops_defence(self, mocked_print, gtfs_fixture): + def test_viz_stops_defence(self, mocked_print, tmpdir, gtfs_fixture): """Check defensive behaviours of viz_stops().""" + tmp = os.path.join(tmpdir, "somefile.html") with pytest.raises( TypeError, match="`out_pth` expected path-like, found ", @@ -167,23 +171,19 @@ def test_viz_stops_defence(self, mocked_print, gtfs_fixture): with pytest.raises( TypeError, match="`geoms` expects a string. Found " ): - gtfs_fixture.viz_stops(out_pth="outputs/somefile.html", geoms=38) + gtfs_fixture.viz_stops(out_pth=tmp, geoms=38) with pytest.raises( ValueError, match="`geoms` must be either 'point' or 'hull." ): - gtfs_fixture.viz_stops( - out_pth="outputs/somefile.html", geoms="foobar" - ) + gtfs_fixture.viz_stops(out_pth=tmp, geoms="foobar") with pytest.raises( TypeError, match="`geom_crs`.*string or integer. Found ", ): - gtfs_fixture.viz_stops( - out_pth="outputs/somefile.html", geom_crs=1.1 - ) + gtfs_fixture.viz_stops(out_pth=tmp, geom_crs=1.1) # check missing stop_id results in print instead of exception gtfs_fixture.feed.stops.drop("stop_id", axis=1, inplace=True) - gtfs_fixture.viz_stops(out_pth="outputs/out.html") + gtfs_fixture.viz_stops(out_pth=tmp) fun_out = mocked_print.mock_calls assert fun_out == [ call("Key Error. Map was not written.") diff --git a/tests/osm/test_osm_utils.py b/tests/osm/test_osm_utils.py index 04ce3803..17e145f2 100644 --- a/tests/osm/test_osm_utils.py +++ b/tests/osm/test_osm_utils.py @@ -30,13 +30,14 @@ def test_filter_osm_defense(self): # out_pth is not a path_like filter_osm(out_pth=False) with pytest.raises( - TypeError, match="`tag_filter` expected boolean. Got " + TypeError, + match="`tag_filter` expected . Got ", ): # check for boolean defense filter_osm(tag_filter=1) with pytest.raises( TypeError, - match="`install_osmosis` expected boolean. Got ", + match="`install_osmosis` .* . Got ", ): # check for boolean defense filter_osm(install_osmosis="False") diff --git a/tests/urban_centres/test_urban_centres.py b/tests/urban_centres/test_urban_centres.py index f43c5c6c..16ad1e41 100644 --- a/tests/urban_centres/test_urban_centres.py +++ b/tests/urban_centres/test_urban_centres.py @@ -13,6 +13,7 @@ import rasterio as rio from contextlib import nullcontext as does_not_raise +from pathlib import Path from pytest_lazyfixture import lazy_fixture from shapely.geometry import Polygon @@ -141,14 +142,19 @@ def outside_cluster_centre(): # test exceptions for input parameters @pytest.mark.parametrize( - "filepath, expected", + "filepath, func, expected", [ - (lazy_fixture("dummy_pop_array"), does_not_raise()), - ("wrongpath", pytest.raises(IOError)), + (lazy_fixture("dummy_pop_array"), "str", does_not_raise()), + (lazy_fixture("dummy_pop_array"), "path", does_not_raise()), + ("wrongpath", "str", pytest.raises(IOError)), ], ) -def test_file(filepath, bbox, cluster_centre, expected): +def test_file(filepath, func, bbox, cluster_centre, expected): """Test filepath.""" + if func == "str": + filepath = str(filepath) + else: + filepath = Path(filepath) with expected: assert ( ucc.UrbanCentre(filepath).get_urban_centre(bbox, cluster_centre) diff --git a/tests/utils/test_defence.py b/tests/utils/test_defence.py index 6bd4b992..412202e7 100644 --- a/tests/utils/test_defence.py +++ b/tests/utils/test_defence.py @@ -65,21 +65,6 @@ def test_check_parent_dir_exists_defence(self): pth="missing/file.someext", param_nm="not_found", create=False ) - error_pth = "test_folder\\test_file.py" - with pytest.raises( - ValueError, - match=re.escape( - "Please specify string paths with single forward" - " slashes only." - f" Got {repr(error_pth)}" - ), - ): - _check_parent_dir_exists( - pth="test_folder\\test_file.py", - param_nm="test_prm", - create=False, - ) - def test_check_parents_dir_exists(self, tmp_path): """Test that a parent directory is created.""" # test without create