From 8e6a53ae7aa143bca778b61c2167d87a518da939 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 2 May 2024 15:02:32 -0400 Subject: [PATCH] validate basemap style is a url (#497) ### Change list - Add validation that the basemap style input is a HTTP url Closes https://github.com/developmentseed/lonboard/issues/485 --- lonboard/_map.py | 8 ++++---- lonboard/traits.py | 24 ++++++++++++++++++++++++ tests/test_map.py | 6 ++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lonboard/_map.py b/lonboard/_map.py index 3e4b05a1..c63648e7 100644 --- a/lonboard/_map.py +++ b/lonboard/_map.py @@ -14,7 +14,7 @@ from lonboard._layer import BaseLayer from lonboard._viewport import compute_view from lonboard.basemap import CartoBasemap -from lonboard.traits import DEFAULT_INITIAL_VIEW_STATE, ViewStateTrait +from lonboard.traits import DEFAULT_INITIAL_VIEW_STATE, BasemapUrl, ViewStateTrait from lonboard.types.map import MapKwargs if TYPE_CHECKING: @@ -156,13 +156,13 @@ def __init__( - Default: `5` """ - basemap_style = traitlets.Unicode(CartoBasemap.PositronNoLabels).tag(sync=True) + basemap_style = BasemapUrl(CartoBasemap.PositronNoLabels) """ - A MapLibre-compatible basemap style. + A URL to a MapLibre-compatible basemap style. Various styles are provided in [`lonboard.basemap`](https://developmentseed.org/lonboard/latest/api/basemap/). - - Type: `str` + - Type: `str`, holding a URL hosting a basemap style. - Default [`lonboard.basemap.CartoBasemap.PositronNoLabels`][lonboard.basemap.CartoBasemap.PositronNoLabels] """ diff --git a/lonboard/traits.py b/lonboard/traits.py index b565238e..1732ed48 100644 --- a/lonboard/traits.py +++ b/lonboard/traits.py @@ -8,6 +8,7 @@ import warnings from typing import Any, List, Optional, Set, Tuple, Union, cast +from urllib.parse import urlparse import matplotlib as mpl import numpy as np @@ -968,3 +969,26 @@ def validate( self.error(obj, value) assert False + + +class BasemapUrl(traitlets.Unicode): + def __init__( + self: TraitType, + *args, + **kwargs: Any, + ) -> None: + super().__init__(*args, **kwargs) + self.tag(sync=True) + + def validate(self, obj: Any, value: Any) -> Any: + value = super().validate(obj, value) + + try: + parsed = urlparse(value) + except: # noqa + self.error(obj, value, info="to be a URL") + + if not parsed.scheme.startswith("http"): + self.error(obj, value, info="to be a HTTP(s) URL") + else: + return value diff --git a/tests/test_map.py b/tests/test_map.py index 76ac759a..7747a3b0 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1,6 +1,7 @@ import geopandas as gpd import pytest import shapely +from traitlets import TraitError from lonboard import Map, ScatterplotLayer, SolidPolygonLayer @@ -27,3 +28,8 @@ def allow_single_layer(): layer = SolidPolygonLayer.from_geopandas(gdf) _m = Map(layer) + + +def test_map_basemap_non_url(): + with pytest.raises(TraitError, match=r"expected to be a HTTP\(s\) URL"): + _m = Map([], basemap_style="hello world")