diff --git a/Makefile b/Makefile index 152595d..d131d58 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test pep8 clean install build publish tree env +.PHONY: test pep8 types clean install build publish tree env test: pep8 py.test --cov=flask_googlemaps -l --tb=short --maxfail=1 tests/ @@ -6,6 +6,9 @@ test: pep8 pep8: @flake8 flask_googlemaps --ignore=F403 +types: + @mypy --py2 flask_googlemaps + clean: @find ./ -name '*.pyc' -exec rm -f {} \; @find ./ -name 'Thumbs.db' -exec rm -f {} \; diff --git a/flask_googlemaps/__init__.py b/flask_googlemaps/__init__.py index 9d7a312..fe7fe00 100644 --- a/flask_googlemaps/__init__.py +++ b/flask_googlemaps/__init__.py @@ -3,12 +3,13 @@ __version__ = "0.4.0" from json import dumps +from typing import Optional, Dict, Any, List, Union, Tuple, Text import requests as rq import requests from flask import Blueprint, Markup, g, render_template -from flask_googlemaps.icons import dots +from flask_googlemaps.icons import dots, Icon DEFAULT_ICON = dots.red DEFAULT_CLUSTER_IMAGE_PATH = "static/images/m" @@ -17,6 +18,37 @@ class Map(object): def __init__( self, + identifier, # type: str + lat, # type: float + lng, # type: float + zoom=13, # type: int + maptype="ROADMAP", # type: str + markers=None, # type: Optional[Union[Dict, List, Tuple]] + varname="map", # type: str + style="height:300px;width:300px;margin:0;", # type: str + cls="map", # type: str + language="en", # type: str + region="US", # type: str + rectangles=None, # type: Optional[List[Union[List, Tuple, Tuple[Tuple], Dict]]] + circles=None, # type: Optional[List[Union[List, Tuple, Dict]]] + polylines=None, # type: Optional[List[Union[List, Tuple, Dict]]] + polygons=None, # type: Optional[List[Union[List, Tuple, Dict]]] + zoom_control=True, # type: bool + maptype_control=True, # type: bool + scale_control=True, # type: bool + streetview_control=True, # type: bool + rotate_control=True, # type: bool + scroll_wheel=True, # type: bool + fullscreen_control=True, # type: bool + collapsible=False, # type: bool + mapdisplay=False, # type: bool + cluster=False, # type: bool + cluster_imagepath=DEFAULT_CLUSTER_IMAGE_PATH, # type: str + cluster_gridsize=60, # type: int + fit_markers_to_bounds=False, # type: bool + center_on_user_location=False, # type: bool + report_clickpos=False, # type: bool + clickpos_uri="", # type: str identifier, lat, lng, @@ -54,6 +86,7 @@ def __init__( bicycle_layer=False, **kwargs ): + # type: (...) -> None """Builds the Map properties""" self.cls = cls self.style = style @@ -63,16 +96,16 @@ def __init__( self.center = self.verify_lat_lng_coordinates(lat, lng) self.zoom = zoom self.maptype = maptype + self.markers = [] # type: List[Any] self.map_ids = map_ids, - self.markers = [] self.build_markers(markers) - self.rectangles = [] + self.rectangles = [] # type: List[Any] self.build_rectangles(rectangles) - self.circles = [] + self.circles = [] # type: List[Any] self.build_circles(circles) - self.polylines = [] + self.polylines = [] # type: List[Any] self.build_polylines(polylines) - self.polygons = [] + self.polygons = [] # type: List[Any] self.build_polygons(polygons) self.identifier = identifier self.zoom_control = zoom_control @@ -99,6 +132,7 @@ def __init__( def build_markers(self, markers): + # type: (Optional[Union[Dict, List, Tuple]]) -> None if not markers: return if not isinstance(markers, (dict, list, tuple)): @@ -118,6 +152,7 @@ def build_markers(self, markers): self.add_marker(**marker_dict) def build_marker_dict(self, marker, icon=None): + # type: (Union[List, Tuple], Optional[Icon]) -> Dict marker_dict = { "lat": marker[0], "lng": marker[1], @@ -129,13 +164,19 @@ def build_marker_dict(self, marker, icon=None): marker_dict["icon"] = marker[3] return marker_dict - def add_marker(self, **kwargs): + def add_marker(self, lat=None, lng=None, **kwargs): + # type: (Optional[float], Optional[float], **Any) -> None + if lat is not None: + kwargs["lat"] = lat + if lng is not None: + kwargs["lng"] = lng if "lat" not in kwargs or "lng" not in kwargs: raise AttributeError("lat and lng required") self.markers.append(kwargs) def build_rectangles(self, rectangles): + # type: (Optional[List[Union[List, Tuple, Tuple[Tuple], Dict]]]) -> None """ Process data to construct rectangles This method is built from the assumption that the rectangles parameter @@ -203,16 +244,17 @@ def build_rectangles(self, rectangles): def build_rectangle_dict( self, - north, - west, - south, - east, - stroke_color="#FF0000", - stroke_opacity=0.8, - stroke_weight=2, - fill_color="#FF0000", - fill_opacity=0.3, + north, # type: float + west, # type: float + south, # type: float + east, # type: float + stroke_color="#FF0000", # type: str + stroke_opacity=0.8, # type: float + stroke_weight=2, # type: int + fill_color="#FF0000", # type: str + fill_opacity=0.3, # type: float ): + # type: (...) -> Dict """ Set a dictionary with the javascript class Rectangle parameters This function sets a default drawing configuration if the user just @@ -239,19 +281,13 @@ def build_rectangle_dict( "stroke_weight": stroke_weight, "fill_color": fill_color, "fill_opacity": fill_opacity, - "bounds": { - "north": north, - "west": west, - "south": south, - "east": east, - }, + "bounds": {"north": north, "west": west, "south": south, "east": east}, } return rectangle - def add_rectangle( - self, north=None, west=None, south=None, east=None, **kwargs - ): + def add_rectangle(self, north=None, west=None, south=None, east=None, **kwargs): + # type: (Optional[float], Optional[float], Optional[float], Optional[float], **Any) -> None """ Adds a rectangle dict to the Map.rectangles attribute The Google Maps API describes a rectangle using the LatLngBounds @@ -286,9 +322,7 @@ def add_rectangle( if east: kwargs["bounds"]["east"] = east - if set(("north", "east", "south", "west")) != set( - kwargs["bounds"].keys() - ): + if set(("north", "east", "south", "west")) != set(kwargs["bounds"].keys()): raise AttributeError("rectangle bounds required to rectangles") kwargs.setdefault("stroke_color", "#FF0000") @@ -300,6 +334,7 @@ def add_rectangle( self.rectangles.append(kwargs) def build_circles(self, circles): + # type: (Optional[List[Union[List, Tuple, Dict]]]) -> None """ Process data to construct rectangles This method is built from the assumption that the circles parameter @@ -336,23 +371,22 @@ def build_circles(self, circles): elif isinstance(circle, (tuple, list)): if len(circle) != 3: raise AttributeError("circle requires center and radius") - circle_dict = self.build_circle_dict( - circle[0], circle[1], circle[2] - ) + circle_dict = self.build_circle_dict(circle[0], circle[1], circle[2]) self.add_circle(**circle_dict) def build_circle_dict( self, - center_lat, - center_lng, - radius, - stroke_color="#FF0000", - stroke_opacity=0.8, - stroke_weight=2, - fill_color="#FF0000", - fill_opacity=0.3, - clickable=True + center_lat, # type: float + center_lng, # type: float + radius, # type: float + stroke_color="#FF0000", # type: str + stroke_opacity=0.8, # type: float + stroke_weight=2, # type: int + fill_color="#FF0000", # type: str + fill_opacity=0.3, # type: float + clickable=True, # type: bool ): + # type: (...) -> Dict """ Set a dictionary with the javascript class Circle parameters This function sets a default drawing configuration if the user just @@ -381,14 +415,13 @@ def build_circle_dict( "fill_opacity": fill_opacity, "center": {"lat": center_lat, "lng": center_lng}, "radius": radius, - "clickable": clickable + "clickable": clickable, } return circle - def add_circle( - self, center_lat=None, center_lng=None, radius=None, **kwargs - ): + def add_circle(self, center_lat=None, center_lng=None, radius=None, **kwargs): + # type: (Optional[float], Optional[float], Optional[float], **Any) -> None """ Adds a circle dict to the Map.circles attribute The circle in a sphere is called "spherical cap" and is defined in the @@ -430,6 +463,7 @@ def add_circle( self.circles.append(kwargs) def build_polylines(self, polylines): + # type: (Optional[List[Union[List, Tuple, Dict]]]) -> None """ Process data to construct polylines This method is built from the assumption that the polylines parameter @@ -493,6 +527,7 @@ def build_polylines(self, polylines): def build_polyline_dict( self, path, stroke_color="#FF0000", stroke_opacity=0.8, stroke_weight=2 ): + # type: (List[Dict], str, float, int) -> Dict """ Set a dictionary with the javascript class Polyline parameters This function sets a default drawing configuration if the user just @@ -524,6 +559,7 @@ def build_polyline_dict( return polyline def add_polyline(self, path=None, **kwargs): + # type: (Optional[List[Dict]], **Any) -> None """ Adds a polyline dict to the Map.polylines attribute The Google Maps API describes a polyline as a "linear overlay of @@ -568,6 +604,7 @@ def add_polyline(self, path=None, **kwargs): self.polylines.append(kwargs) def build_polygons(self, polygons): + # type: (Optional[List[Union[List, Tuple, Dict]]]) -> None """ Process data to construct polygons This method is built from the assumption that the polygons parameter @@ -632,13 +669,14 @@ def build_polygons(self, polygons): def build_polygon_dict( self, - path, - stroke_color="#FF0000", - stroke_opacity=0.8, - stroke_weight=2, - fill_color="#FF0000", - fill_opacity=0.3, + path, # type: List[Dict] + stroke_color="#FF0000", # type: str + stroke_opacity=0.8, # type: float + stroke_weight=2, # type: int + fill_color="#FF0000", # type: str + fill_opacity=0.3, # type: float ): + # type: (...) -> Dict """ Set a dictionary with the javascript class Polygon parameters This function sets a default drawing configuration if the user just @@ -676,6 +714,7 @@ def build_polygon_dict( return polygon def add_polygon(self, path=None, **kwargs): + # type: (Optional[List[Dict]], **Any) -> None """ Adds a polygon dict to the Map.polygons attribute The Google Maps API describes a polyline as a "linear overlay of @@ -723,9 +762,11 @@ def add_polygon(self, path=None, **kwargs): self.polygons.append(kwargs) def render(self, *args, **kwargs): + # type: (*Any, **Any) -> Text return render_template(*args, **kwargs) def as_json(self): + # type: () -> Dict json_dict = { "identifier": self.identifier, "center": self.center, @@ -767,41 +808,47 @@ def verify_lat_lng_coordinates(self, lat, lng): @property def js(self): + # type: () -> Markup return Markup( - self.render( - "googlemaps/gmapjs.html", gmap=self, DEFAULT_ICON=DEFAULT_ICON - ) + self.render("googlemaps/gmapjs.html", gmap=self, DEFAULT_ICON=DEFAULT_ICON) ) @property def html(self): + # type: () -> Markup return Markup(self.render("googlemaps/gmap.html", gmap=self)) def googlemap_obj(*args, **kwargs): + # type: (*Any, **Any) -> Map map = Map(*args, **kwargs) return map def googlemap(*args, **kwargs): + # type: (*Any, **Any) -> Markup map = googlemap_obj(*args, **kwargs) return Markup("".join((map.js, map.html))) def googlemap_html(*args, **kwargs): + # type: (*Any, **Any) -> Markup return googlemap_obj(*args, **kwargs).html def googlemap_js(*args, **kwargs): + # type: (*Any, **Any) -> Markup return googlemap_obj(*args, **kwargs).js def set_googlemaps_loaded(): + # type: () -> str g.googlemaps_loaded = True return "" def get_address(API_KEY, lat, lon): + # type: (str, float, float) -> dict add_dict = dict() response = rq.get( "https://maps.googleapis.com/maps/api/geocode/json?latlng=" @@ -809,29 +856,19 @@ def get_address(API_KEY, lat, lon): + "&key=" + API_KEY ).json() - add_dict["zip"] = response["results"][0]["address_components"][-1][ - "long_name" - ] - add_dict["country"] = response["results"][0]["address_components"][-2][ - "long_name" - ] - add_dict["state"] = response["results"][0]["address_components"][-3][ - "long_name" - ] - add_dict["city"] = response["results"][0]["address_components"][-4][ - "long_name" - ] - add_dict["locality"] = response["results"][0]["address_components"][-5][ - "long_name" - ] - add_dict["road"] = response["results"][0]["address_components"][-6][ - "long_name" - ] + add_dict["zip"] = response["results"][0]["address_components"][-1]["long_name"] + add_dict["country"] = response["results"][0]["address_components"][-2]["long_name"] + add_dict["state"] = response["results"][0]["address_components"][-3]["long_name"] + add_dict["city"] = response["results"][0]["address_components"][-4]["long_name"] + add_dict["locality"] = response["results"][0]["address_components"][-5]["long_name"] + add_dict["road"] = response["results"][0]["address_components"][-6]["long_name"] add_dict["formatted_address"] = response["results"][0]["formatted_address"] return add_dict def get_coordinates(API_KEY, address_text): + # type: (str, str) -> Dict + response = requests.get( response = rq.get( "https://maps.googleapis.com/maps/api/geocode/json?address=" + address_text @@ -842,16 +879,19 @@ def get_coordinates(API_KEY, address_text): def is_googlemaps_loaded(): + # type: () -> bool return getattr(g, "googlemaps_loaded", False) class GoogleMaps(object): def __init__(self, app=None, **kwargs): + # type: (Optional[Any], **str) -> None self.key = kwargs.get("key") if app: self.init_app(app) def init_app(self, app): + # type: (Any) -> None if self.key: app.config["GOOGLEMAPS_KEY"] = self.key self.register_blueprint(app) @@ -860,9 +900,7 @@ def init_app(self, app): app.add_template_global(googlemap_obj) app.add_template_filter(googlemap) app.add_template_global(googlemap) - app.add_template_global( - app.config.get("GOOGLEMAPS_KEY"), name="GOOGLEMAPS_KEY" - ) + app.add_template_global(app.config.get("GOOGLEMAPS_KEY"), name="GOOGLEMAPS_KEY") app.add_template_global(set_googlemaps_loaded) app.add_template_global(is_googlemaps_loaded) diff --git a/flask_googlemaps/icons.py b/flask_googlemaps/icons.py index 1e0f0e2..ccdbc50 100644 --- a/flask_googlemaps/icons.py +++ b/flask_googlemaps/icons.py @@ -9,11 +9,14 @@ __all__ = ["dots", "alpha", "shapes", "pushpin", "paddle"] +from typing import Optional, List + class Icon(object): """Dynamically return dot icon url""" def __init__(self, base_url, options=None): + # type: (str, Optional[List[str]]) -> None self.base_url = base_url self.options = options @@ -24,7 +27,7 @@ def __getattr__(self, item): dots = Icon( base_url="//maps.google.com/mapfiles/ms/icons/{0}-dot.png", options=["blue", "yellow", "green", "red", "pink", "purple", "red"], -) +) # type: Icon alpha = Icon( base_url="//www.google.com/mapfiles/marker{0}.png", @@ -56,7 +59,7 @@ def __getattr__(self, item): "W", "Y", ], -) +) # type: Icon shapes = Icon( base_url="//maps.google.com/mapfiles/kml/shapes/{0}.png", @@ -166,7 +169,7 @@ def __getattr__(self, item): "woman", "yen", ], -) +) # type: Icon pushpin = Icon( base_url="//maps.google.com/mapfiles/kml/pushpin/{0}.png", options=[ @@ -179,7 +182,7 @@ def __getattr__(self, item): "wht-pushpin", "ylw-pushpin", ], -) +) # type: Icon paddle = Icon( base_url="//maps.google.com/mapfiles/kml/paddle/{0}.png", @@ -310,4 +313,4 @@ def __getattr__(self, item): "stop", "route", ], -) +) # type: Icon diff --git a/flask_googlemaps/py.typed b/flask_googlemaps/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock index 46b2ab4..5d447ba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -538,10 +538,31 @@ optional = false python-versions = ">=3.5" [[package]] +description = "Optional static typing for Python" +name = "mypy" +optional = false +python-versions = ">=3.5" +version = "0.790" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +category = "dev" +description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +optional = false +python-versions = "*" version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" + +[[package]] +category = "main" +description = "Natural Language Toolkit" optional = false python-versions = "*" @@ -906,17 +927,15 @@ python-versions = "*" [[package]] category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" -name = "wcwidth" - - -[[package]] -name = "typing-extensions" -version = "3.7.4.3" description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" +name = "typing-extensions" optional = false python-versions = "*" +version = "3.7.4.3" + +[[package]] +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" [[package]] name = "urllib3" @@ -984,7 +1003,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "fca7cbff51d0683d138bf96f7de7c17397a276b56e288d4fbdfb006ebbb97a9a" +content-hash = "d145b170ae9fb4b3cb6b0215b8b6172e863315c5cbf1de0ffdc7250ca7f83b34" lock-version = "1.1" python-versions = ">=3.6" content-hash = "216815d91eebbe298c7087122c45cbd214ce50afa100b1cd9e958156c85e258e" @@ -1254,6 +1273,26 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +mypy = [ + {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, + {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, + {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, + {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, + {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, + {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, + {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, + {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, + {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, + {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, + {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, + {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, + {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, + {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] nltk = [ {file = "nltk-3.5.zip", hash = "sha256:845365449cd8c5f9731f7cb9f8bd6fd0767553b9d53af9eb1b3abf7700936b35"}, ] diff --git a/pyproject.toml b/pyproject.toml index 58a1fce..eaf1179 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ classifiers=[ packages = [ {include = "flask_googlemaps"} ] +include = ["flask_googlemaps/py.typed"] [tool.poetry.dependencies] python = ">=3.6" @@ -40,6 +41,7 @@ pylint = "^2.4.4" Pygments = "^2.6.1" pytest = "^5.4.1" pytest-cov = "^2.8.1" +mypy = "^0.790" [build-system] requires = ["poetry>=1.1.2"]