From 0a51c3b4ca46607bade906f1ff17f2777a099902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Thu, 31 Jan 2019 18:12:26 +0100 Subject: [PATCH] =?UTF-8?q?Refactor=20tile=5Fproviders=20to=20handle=20mul?= =?UTF-8?q?tiple=20documents=20more=20intuitively=E2=80=A6=20(#8593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor tile_providers to handle multiple documents more intuitively. Closes #8566 * Fix code-review issues * Allow lower case string-based calls to get_provider * Rename ThirdParty to Vendors and make deprecation visible from code * Remove trailing whitespace --- bokeh/tests/test_tile_providers.py | 67 ++++++-- bokeh/tile_providers.py | 166 +++++++++++++------ examples/integration/tiles/tile_smoothing.py | 7 +- examples/plotting/file/tile_source.py | 4 +- 4 files changed, 178 insertions(+), 66 deletions(-) diff --git a/bokeh/tests/test_tile_providers.py b/bokeh/tests/test_tile_providers.py index 5ac9524fcf5..9339c7f4930 100644 --- a/bokeh/tests/test_tile_providers.py +++ b/bokeh/tests/test_tile_providers.py @@ -39,6 +39,8 @@ 'STAMEN_TONER', 'STAMEN_TONER_BACKGROUND', 'STAMEN_TONER_LABELS', + 'get_provider', + 'Vendors' ) _CARTO_URLS = { @@ -72,15 +74,20 @@ @pytest.mark.unit class Test_StamenProviders(object): def test_type(self, name): - p = getattr(bt, name) + with pytest.deprecated_call(): + p = getattr(bt, name) assert isinstance(p, WMTSTileSource) def test_url(self, name): - p = getattr(bt, name) + with pytest.deprecated_call(): + p = getattr(bt, name) assert p.url == _STAMEN_URLS[name] def test_attribution(self, name): - p = getattr(bt, name) + with pytest.deprecated_call(): + p = getattr(bt, name) + + print(p.attribution) assert p.attribution == ( 'Map tiles by Stamen Design, ' 'under CC BY 3.0. ' @@ -89,32 +96,72 @@ def test_attribution(self, name): ) % _STAMEN_LIC[name] def test_copies(self, name): - p1 = getattr(bt, name) - p2 = getattr(bt, name) + with pytest.deprecated_call(): + p1 = getattr(bt, name) + p2 = getattr(bt, name) assert p1 is not p2 @pytest.mark.parametrize('name', ['CARTODBPOSITRON', 'CARTODBPOSITRON_RETINA']) @pytest.mark.unit class Test_CartoProviders(object): def test_type(self, name): - p = getattr(bt, name) + with pytest.deprecated_call(): + p = getattr(bt, name) assert isinstance(p, WMTSTileSource) def test_url(self, name): - p = getattr(bt, name) + with pytest.deprecated_call(): + p = getattr(bt, name) assert p.url == _CARTO_URLS[name] def test_attribution(self, name): - p = getattr(bt, name) + with pytest.deprecated_call(): + p = getattr(bt, name) assert p.attribution == ( '© OpenStreetMap contributors,' '© CartoDB' ) def test_copies(self, name): - p1 = getattr(bt, name) - p2 = getattr(bt, name) + with pytest.deprecated_call(): + p1 = getattr(bt, name) + p2 = getattr(bt, name) + assert p1 is not p2 + + +@pytest.mark.unit +class Test_GetProvider(object): + + @pytest.mark.parametrize('name', ['CARTODBPOSITRON', 'CARTODBPOSITRON_RETINA', 'STAMEN_TERRAIN', + 'STAMEN_TERRAIN_RETINA', 'STAMEN_TONER', 'STAMEN_TONER_BACKGROUND', + 'STAMEN_TONER_LABELS', ]) + def test_get_provider(self, name): + assert name in bt.Vendors + enum_member = getattr(bt.Vendors, name) + p1 = bt.get_provider(enum_member) + p2 = bt.get_provider(name) + p3 = bt.get_provider(name.lower()) + assert isinstance(p1, WMTSTileSource) + assert isinstance(p2, WMTSTileSource) + assert isinstance(p3, WMTSTileSource) assert p1 is not p2 + assert p2 is not p3 + assert p1 is not p3 + assert p1.url == p2.url == p3.url + assert p1.attribution == p2.attribution == p3.attribution + + with pytest.deprecated_call(): + # This will not return a WMTSTileSource in bokeh 2.0.0! + default_instance = getattr(bt, name) + new_instance = bt.get_provider(default_instance) + assert default_instance is not new_instance + assert default_instance.url == new_instance.url + assert default_instance.attribution == new_instance.attribution + + def test_unknown_vendor(self): + with pytest.raises(ValueError): + bt.get_provider("This is not a valid tile vendor") + #----------------------------------------------------------------------------- # Dev API diff --git a/bokeh/tile_providers.py b/bokeh/tile_providers.py index 77e8292f8ae..7636d0d79e3 100644 --- a/bokeh/tile_providers.py +++ b/bokeh/tile_providers.py @@ -9,9 +9,42 @@ Attributes: + .. bokeh-enum:: Vendors + :module: bokeh.tile_providers + + get_provider + Use this function to retrieve an instance of a predefined tile provider. + + Args: + provider_name (Union[str, Vendors]) + Name of the tile provider to supply. + Use tile_providers.Vendors enum, or the name of a provider as string + + Returns: + WMTSTileProviderSource: The desired tile provider instance + + Raises: + ValueError, if the provider can not be found + ValueError, if the attribution for that provider can not be found + + + Example: + + .. code-block:: python + + >>> from bokeh.tile_providers import get_provider, Vendors + >>> get_provider(Vendors.CARTODBPOSITRON) + + >>> get_provider('CARTODBPOSITRON') + + + CARTODBPOSITRON Tile Source for CartoDB Tile Service + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -19,6 +52,9 @@ CARTODBPOSITRON_RETINA Tile Source for CartoDB Tile Service (tiles at 'retina' resolution) + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -26,6 +62,9 @@ STAMEN_TERRAIN Tile Source for Stamen Terrain Service + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -33,6 +72,9 @@ STAMEN_TERRAIN_RETINA Tile Source for Stamen Terrain Service + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -40,6 +82,9 @@ STAMEN_TONER Tile Source for Stamen Toner Service + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -47,6 +92,9 @@ STAMEN_TONER_BACKGROUND Tile Source for Stamen Toner Background Service which does not include labels + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -54,6 +102,9 @@ STAMEN_TONER_LABELS Tile Source for Stamen Toner Service which includes only labels + Warns: + BokehDeprecationWarning: Deprecated in bokeh 1.1.0. Use get_provider instead + .. raw:: html @@ -71,6 +122,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging + log = logging.getLogger(__name__) #----------------------------------------------------------------------------- @@ -84,6 +136,8 @@ # External imports # Bokeh imports +from bokeh.core.enums import enumeration + #----------------------------------------------------------------------------- # Globals and constants @@ -103,8 +157,18 @@ # Private API #----------------------------------------------------------------------------- -class _TileProvidersModule(types.ModuleType): +# Can be removed in bokeh 2.0 +def _make_deprecated_property(name): + + def deprecated_property_tile(self): + from bokeh.util.deprecation import deprecated + deprecated(since_or_msg=(1, 1, 0), old=name, new='get_provider(Vendors.%s)' % name) + return self.get_provider(provider_name=name) + func = property(deprecated_property_tile) + return func + +class _TileProvidersModule(types.ModuleType): _CARTO_ATTRIBUTION = ( '© OpenStreetMap contributors,' '© CartoDB' @@ -117,63 +181,61 @@ class _TileProvidersModule(types.ModuleType): 'under %s.' ) - # Properties -------------------------------------------------------------- + _SERVICE_URLS = dict( + CARTODBPOSITRON='https://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', + CARTODBPOSITRON_RETINA='https://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png', + STAMEN_TERRAIN='http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png', + STAMEN_TERRAIN_RETINA='http://tile.stamen.com/terrain/{Z}/{X}/{Y}@2x.png', + STAMEN_TONER='http://tile.stamen.com/toner/{Z}/{X}/{Y}.png', + STAMEN_TONER_BACKGROUND='http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png', + STAMEN_TONER_LABELS='http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png', + ) - @property - def CARTODBPOSITRON(self): - from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='https://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', - attribution=self._CARTO_ATTRIBUTION - ) + _STAMEN_ATTRIBUTION_URLS = dict( + STAMEN_TERRAIN='CC BY SA', + STAMEN_TERRAIN_RETINA='CC BY SA', + STAMEN_TONER='ODbL', + STAMEN_TONER_BACKGROUND='ODbL', + STAMEN_TONER_LABELS='ODbL', + ) - @property - def CARTODBPOSITRON_RETINA(self): - from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='https://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png', - attribution=self._CARTO_ATTRIBUTION - ) + Vendors = enumeration('CARTODBPOSITRON', 'CARTODBPOSITRON_RETINA', + 'STAMEN_TERRAIN', 'STAMEN_TERRAIN_RETINA', 'STAMEN_TONER', + 'STAMEN_TONER_BACKGROUND', 'STAMEN_TONER_LABELS', + case_sensitive=True) - @property - def STAMEN_TERRAIN(self): + def get_provider(self, provider_name): from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png', - attribution=self._STAMEN_ATTRIBUTION % 'CC BY SA' - ) - @property - def STAMEN_TERRAIN_RETINA(self): - from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='http://tile.stamen.com/terrain/{Z}/{X}/{Y}@2x.png', - attribution=self._STAMEN_ATTRIBUTION % 'CC BY SA' - ) + if isinstance(provider_name, WMTSTileSource): + # This allows `get_provider(CARTODBPOSITRON)` to work + return WMTSTileSource(url=provider_name.url, attribution=provider_name.attribution) - @property - def STAMEN_TONER(self): - from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='http://tile.stamen.com/toner/{Z}/{X}/{Y}.png', - attribution=self._STAMEN_ATTRIBUTION % 'ODbL' - ) + selected_provider = provider_name.upper() - @property - def STAMEN_TONER_BACKGROUND(self): - from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png', - attribution=self._STAMEN_ATTRIBUTION % 'ODbL' - ) + if selected_provider not in self.Vendors: + raise ValueError('Unknown tile provider %s' % provider_name) + + url = self._SERVICE_URLS[selected_provider] + if selected_provider.startswith('CARTO'): + attribution = self._CARTO_ATTRIBUTION + elif selected_provider.startswith('STAMEN'): + attribution = self._STAMEN_ATTRIBUTION % self._STAMEN_ATTRIBUTION_URLS[selected_provider] + else: + raise ValueError('Can not retrieve attribution for %s' % selected_provider) + return WMTSTileSource(url=url, attribution=attribution) + + # Properties -------------------------------------------------------------- + + # For bokeh 2.0 these can easily be replaced with their corresponding enum values + CARTODBPOSITRON = _make_deprecated_property(Vendors.CARTODBPOSITRON) + CARTODBPOSITRON_RETINA = _make_deprecated_property(Vendors.CARTODBPOSITRON_RETINA) + STAMEN_TERRAIN = _make_deprecated_property(Vendors.STAMEN_TERRAIN) + STAMEN_TERRAIN_RETINA = _make_deprecated_property(Vendors.STAMEN_TERRAIN_RETINA) + STAMEN_TONER = _make_deprecated_property(Vendors.STAMEN_TONER) + STAMEN_TONER_BACKGROUND = _make_deprecated_property(Vendors.STAMEN_TONER_BACKGROUND) + STAMEN_TONER_LABELS = _make_deprecated_property(Vendors.STAMEN_TONER_LABELS) - @property - def STAMEN_TONER_LABELS(self): - from bokeh.models.tiles import WMTSTileSource - return WMTSTileSource( - url='http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png', - attribution=self._STAMEN_ATTRIBUTION % 'ODbL' - ) #----------------------------------------------------------------------------- # Code @@ -189,6 +251,8 @@ def STAMEN_TONER_LABELS(self): 'STAMEN_TONER', 'STAMEN_TONER_BACKGROUND', 'STAMEN_TONER_LABELS', + 'get_provider', + 'Vendors' ) sys.modules['bokeh.tile_providers'] = _mod -del _mod, sys, types +del _mod, sys, types, _make_deprecated_property diff --git a/examples/integration/tiles/tile_smoothing.py b/examples/integration/tiles/tile_smoothing.py index ea7e7c2f309..b94505dfba9 100644 --- a/examples/integration/tiles/tile_smoothing.py +++ b/examples/integration/tiles/tile_smoothing.py @@ -1,6 +1,6 @@ from bokeh.layouts import column from bokeh.plotting import figure, show, output_file -from bokeh.tile_providers import CARTODBPOSITRON +from bokeh.tile_providers import get_provider, Vendors output_file("tile_smoothing.html") @@ -14,9 +14,10 @@ p2.y_range = p1.y_range # Add smoothed tiles (the default) -r1 = p1.add_tile(CARTODBPOSITRON) +tile_provider = get_provider(Vendors.CARTODBPOSITRON) +r1 = p1.add_tile(tile_provider) # Add non-smoothed tile -r2 = p2.add_tile(CARTODBPOSITRON, smoothing=False) +r2 = p2.add_tile(tile_provider, smoothing=False) show(column(p1, p2)) diff --git a/examples/plotting/file/tile_source.py b/examples/plotting/file/tile_source.py index be492350e1c..43b98c50357 100644 --- a/examples/plotting/file/tile_source.py +++ b/examples/plotting/file/tile_source.py @@ -1,11 +1,11 @@ from bokeh.plotting import figure, show, output_file -from bokeh.tile_providers import CARTODBPOSITRON +from bokeh.tile_providers import get_provider, Vendors output_file("tile_source.html") # create plot and add tools p = figure(x_range=(-2000000, 2000000), y_range=(1000000, 7000000), x_axis_type="mercator", y_axis_type="mercator") -p.add_tile(CARTODBPOSITRON) +p.add_tile(get_provider(Vendors.CARTODBPOSITRON)) show(p)