From 05e62d76a06a4bda0151028b7be7262c46911d7e Mon Sep 17 00:00:00 2001 From: collijk Date: Sun, 28 Apr 2024 14:53:41 -0700 Subject: [PATCH 1/3] Add Bunch methods to allow live updates and loading from json --- .gitignore | 3 +++ xyzservices/lib.py | 52 ++++++++++++++++++++++++---------------- xyzservices/providers.py | 4 ++-- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index dda7784..19c6863 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,9 @@ ENV/ env.bak/ venv.bak/ +# Pycharm files +.idea/ + # Spyder project settings .spyderproject .spyproject diff --git a/xyzservices/lib.py b/xyzservices/lib.py index 42e3668..c40c89e 100644 --- a/xyzservices/lib.py +++ b/xyzservices/lib.py @@ -39,12 +39,36 @@ class Bunch(dict): 'https://myserver.com/bw/{z}/{x}/{y}' """ + def __init__(self, *args, **kwargs): + super().__init__() + self.update(*args, **kwargs) + + @classmethod + def from_json(cls, f): + data = json.loads(f) + providers = Bunch() + providers.update(**data) + return providers + def __getattr__(self, key): try: return self.__getitem__(key) except KeyError as err: raise AttributeError(key) from err + def __setitem__(self, provider_name: str, provider: dict | TileProvider | Bunch) -> None: + if "url" in provider: + super().__setitem__(provider_name, TileProvider(provider)) + else: + sub_providers = Bunch( + {i: TileProvider(provider[i]) for i in provider} + ) + super().__setitem__(provider_name, sub_providers) + + def update(self, *args, **kwargs): + for k, v in dict(*args, **kwargs).items(): + self[k] = v + def __dir__(self): return self.keys() @@ -108,7 +132,6 @@ def flatten(self) -> dict: 207 """ - flat = {} def _get_providers(provider): @@ -292,7 +315,7 @@ def query_name(self, name: str) -> TileProvider: raise ValueError(f"No matching provider found for the query '{name}'.") -class TileProvider(Bunch): +class TileProvider(dict): """ A dict with attribute-access and that can be called to update keys @@ -369,6 +392,12 @@ def __init__(self, *args, **kwargs): ) raise AttributeError(msg) + def __getattr__(self, key): + try: + return self.__getitem__(key) + except KeyError as err: + raise AttributeError(key) from err + def __call__(self, **kwargs) -> TileProvider: new = TileProvider(self) # takes a copy preserving the class new.update(kwargs) @@ -584,25 +613,6 @@ def from_qms(cls, name: str) -> TileProvider: ) -def _load_json(f): - data = json.loads(f) - - providers = Bunch() - - for provider_name in data: - provider = data[provider_name] - - if "url" in provider: - providers[provider_name] = TileProvider(provider) - - else: - providers[provider_name] = Bunch( - {i: TileProvider(provider[i]) for i in provider} - ) - - return providers - - CSS_STYLE = """ /* CSS stylesheet for displaying xyzservices objects in Jupyter.*/ .xyz-wrap { diff --git a/xyzservices/providers.py b/xyzservices/providers.py index 8cb3d8c..9fe858f 100644 --- a/xyzservices/providers.py +++ b/xyzservices/providers.py @@ -2,7 +2,7 @@ import pkgutil import sys -from .lib import _load_json +from .lib import Bunch data_path = os.path.join(sys.prefix, "share", "xyzservices", "providers.json") @@ -12,4 +12,4 @@ else: json = pkgutil.get_data("xyzservices", "data/providers.json") -providers = _load_json(json) +providers = Bunch.from_json(json) From 1ccbbb7bb5d3585dfdaca633fbb650e713b6e58d Mon Sep 17 00:00:00 2001 From: collijk Date: Sun, 28 Apr 2024 14:57:11 -0700 Subject: [PATCH 2/3] Simplify from_json --- xyzservices/lib.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xyzservices/lib.py b/xyzservices/lib.py index c40c89e..27d63fa 100644 --- a/xyzservices/lib.py +++ b/xyzservices/lib.py @@ -45,10 +45,7 @@ def __init__(self, *args, **kwargs): @classmethod def from_json(cls, f): - data = json.loads(f) - providers = Bunch() - providers.update(**data) - return providers + return cls(**json.loads(f)) def __getattr__(self, key): try: From 0c01a7dc789c0dfe0556c8f0843555ec4b2a7ce7 Mon Sep 17 00:00:00 2001 From: collijk Date: Sun, 6 Oct 2024 20:23:45 -0700 Subject: [PATCH 3/3] Checkpoint --- doc/source/introduction.ipynb | 225 +++++----------------------------- xyzservices/lib.py | 6 +- 2 files changed, 34 insertions(+), 197 deletions(-) diff --git a/doc/source/introduction.ipynb b/doc/source/introduction.ipynb index fa69fe1..a7db75c 100644 --- a/doc/source/introduction.ipynb +++ b/doc/source/introduction.ipynb @@ -13,8 +13,8 @@ "source": [ "import xyzservices.providers as xyz" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -36,8 +36,8 @@ "source": [ "xyz" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -52,8 +52,8 @@ "source": [ "xyz.OpenStreetMap" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -68,8 +68,8 @@ "source": [ "type(xyz.OpenStreetMap.Mapnik)" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -84,8 +84,8 @@ "source": [ "xyz.OpenStreetMap.Mapnik" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -134,8 +134,8 @@ "source": [ "xyz.OpenWeatherMap.Clouds" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "code", @@ -143,8 +143,8 @@ "source": [ "xyz.OpenWeatherMap.Clouds.requires_token()" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -163,8 +163,8 @@ "# Calling the object will return a copy\n", "xyz.OpenWeatherMap.Clouds(apiKey=\"my-private-api-key\")" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -206,8 +206,8 @@ " attr=tiles.html_attribution,\n", ")" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -222,8 +222,8 @@ "source": [ "xyz.OpenWeatherMap.Clouds.build_url(x=12, y=21, z=15, apiKey=\"my-private-api-key\")" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -245,8 +245,8 @@ "source": [ "providers = xyz.flatten()" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -261,8 +261,8 @@ "source": [ "len(providers)" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -287,8 +287,8 @@ " 'CartoDB.Voyager'\n", " ]" ], - "outputs": [], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -315,22 +315,8 @@ "\n", "m" ], - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ] - }, - "metadata": {}, - "execution_count": 24 - } - ], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -350,157 +336,8 @@ "qms_provider = TileProvider.from_qms(\"OpenTopoMap\")\n", "qms_provider" ], - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'name': 'OpenTopoMap',\n", - " 'url': 'https://tile.opentopomap.org/{z}/{x}/{y}.png',\n", - " 'min_zoom': 0,\n", - " 'max_zoom': 19,\n", - " 'attribution': 'OpenTopoMap (CC-BY-SA)'}" - ], - "text/html": [ - "\n", - "
\n", - " \n", - "
\n", - "
\n", - "
xyzservices.TileProvider
\n", - "
OpenTopoMap
\n", - "
\n", - "
\n", - "
\n", - "
url
https://tile.opentopomap.org/{z}/{x}/{y}.png
min_zoom
0
max_zoom
19
attribution
OpenTopoMap (CC-BY-SA)
\n", - "
\n", - "
\n", - "
\n", - "
\n", - " " - ] - }, - "metadata": {}, - "execution_count": 25 - } - ], - "metadata": {} + "metadata": {}, + "outputs": [] }, { "cell_type": "markdown", @@ -535,4 +372,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/xyzservices/lib.py b/xyzservices/lib.py index 27d63fa..41b1911 100644 --- a/xyzservices/lib.py +++ b/xyzservices/lib.py @@ -6,13 +6,13 @@ import json import urllib.request import uuid -from typing import Callable +from typing import Callable, Dict, Union from urllib.parse import quote QUERY_NAME_TRANSLATION = str.maketrans({x: "" for x in "., -_/"}) -class Bunch(dict): +class Bunch(Dict[str, Union["TileProvider", "Bunch"]]): """A dict with attribute-access :class:`Bunch` is used to store :class:`TileProvider` objects. @@ -39,7 +39,7 @@ class Bunch(dict): 'https://myserver.com/bw/{z}/{x}/{y}' """ - def __init__(self, *args, **kwargs): + def __init__(self, raw_bunch_dict: , **kwargs): super().__init__() self.update(*args, **kwargs)