diff --git a/docs/notebooks/README.md b/docs/notebooks/README.md index ab8d716..10f8116 100644 --- a/docs/notebooks/README.md +++ b/docs/notebooks/README.md @@ -3,11 +3,12 @@ The example notebooks in this directory demonstrate various functionalities of `HERE Location Services`. ## Prerequisites + Before you run the Notebooks make sure you have: + - A HERE developer account, free and available under [HERE Developer Portal](https://developer.here.com) - An [API key](https://developer.here.com/documentation/identity-access-management/dev_guide/topics/dev-apikey.html) from the [HERE Developer Portal](https://developer.here.com) - ## Preparing for visualization In order to run these Notebooks, you will need a few third-party dependencies. Please copy the following text to a file name anything you like, e.g. `requirements.txt`: @@ -23,10 +24,11 @@ For visualization requirements please install [here-map-widget-for-jupyter](http Follow installation steps from here: [here-map-widget-for-jupyter](https://github.com/heremaps/here-map-widget-for-jupyter#installation). - ## Notebooks - [Location Services](./location_services.ipynb) - Examples of various location services. - [Restaurant Search](./isoline_routing_restaurant_search.ipynb) - Usecase of restarant search using isoline routing. - [Routing](./routing.ipynb) - Examples of routing API. -- [Matrix Routing](./matrix_routing.ipynb) - Examples of Matrix routing API. \ No newline at end of file +- [Matrix Routing](./matrix_routing.ipynb) - Examples of Matrix routing API. +- [Autosuggest](./autosuggest.ipynb) - Examples of Autosuggest API. +- [Destination Weather](./destination_weather.ipynb) - Examples of Destination Weather API. diff --git a/docs/notebooks/destination_weather.ipynb b/docs/notebooks/destination_weather.ipynb new file mode 100644 index 0000000..18aaa48 --- /dev/null +++ b/docs/notebooks/destination_weather.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import os\n", + "os.environ[\"LS_API_KEY\"] = \"MY-API-KEY\" # replace your API key here." + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import os\n", + "\n", + "from here_location_services import LS\n", + "from here_map_widget import Map, MarkerCluster, ObjectLayer\n", + "from here_location_services.config.dest_weather_config import DEST_WEATHER_PRODUCT\n", + "\n", + "\n", + "LS_API_KEY = os.environ.get(\"LS_API_KEY\")\n", + "ls = LS(api_key=LS_API_KEY)\n", + "\n", + "result1 = ls.get_dest_weather(\n", + " at=[19.1503, 72.8530],\n", + " products=[DEST_WEATHER_PRODUCT.observation]\n", + ")\n", + "\n", + "results = []\n", + "m = Map(\n", + " api_key=LS_API_KEY,\n", + " center=[19.1621, 73.0008],\n", + " zoom=7,\n", + ")\n", + "for observation in result1.places[0][\"observations\"]:\n", + " results.append(\n", + " dict(\n", + " lat=observation[\"place\"][\"location\"][\"lat\"],\n", + " lng=observation[\"place\"][\"location\"][\"lng\"],\n", + " data=observation[\"description\"] + \" \" + str(observation[\"temperature\"]) + \"C\",\n", + " )\n", + " )\n", + "\n", + "provider = MarkerCluster(data_points=results, show_bubble=True)\n", + "layer = ObjectLayer(provider=provider)\n", + "m.add_layer(layer)\n", + "m" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "from geojson import Point\n", + "from datetime import datetime\n", + "\n", + "result2 = ls.get_weather_alerts(\n", + " geometry=Point(coordinates=[15.256, 23.456]),\n", + " start_time=datetime.now(),\n", + " width=3000,\n", + " )\n", + "\n", + "print(result2)" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [], + "outputs": [], + "metadata": {} + } + ], + "metadata": { + "orig_nbformat": 4, + "language_info": { + "name": "python", + "version": "3.9.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3.9.6 64-bit" + }, + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/docs/source/dest_weather.rst b/docs/source/dest_weather.rst new file mode 100644 index 0000000..0203e3b --- /dev/null +++ b/docs/source/dest_weather.rst @@ -0,0 +1,96 @@ +Destination Weather +==================== +`Destination Weather API `_ provides weather forecasts and reports on current weather conditions. It also provides information on severe weather alerts along a specified route or a single car location. + +Example +------- + +.. jupyter-execute:: + + import os + from here_location_services import LS + from here_map_widget import Map, MarkerCluster, ObjectLayer + from here_location_services.config.dest_weather_config import DEST_WEATHER_PRODUCT + + + LS_API_KEY = os.environ.get("LS_API_KEY") + ls = LS(api_key=LS_API_KEY) + + result1 = ls.get_dest_weather( + at=[19.1503, 72.8530], + products=[DEST_WEATHER_PRODUCT.observation] + ) + + results = [] + m = Map( + api_key=LS_API_KEY, + center=[19.1621, 73.0008], + zoom=7, + ) + for observation in result1.places[0]["observations"]: + results.append( + dict( + lat=observation["place"]["location"]["lat"], + lng=observation["place"]["location"]["lng"], + data=observation["description"] + " " + str(observation["temperature"]) + "C", + ) + ) + + provider = MarkerCluster(data_points=results, show_bubble=True) + layer = ObjectLayer(provider=provider) + m.add_layer(layer) + m + +Attributes +---------- + +==================== =============================================================================================================== === +Attribute Type Doc +==================== =============================================================================================================== === +products list of :class:`DestWeatherProduct ` List of strings identifying the type of report to obtain. +at list optional A list of ``latitude`` and ``longitude`` specifying the area covered by the weather report. +query str optional Free text query. Examples: "125, Berliner, berlin", "Beacon, Boston" +zipcode str optional ZIP code of the location. This parameter is supported only for locations in the United States of America. +hourly_date :func:`datetime.datetime` optional Date for which hourly forecasts are to be retrieved. +one_observation bool optional Boolean, if set to true, the response only includes the closest location. Only available when the `product` parameter is set to `DEST_WEATHER_PRODUCT.observation`. +language str optional Defines the language used in the descriptions in the response. +units :class:`DestWeatherUnits ` optional Defines whether units or imperial units are used in the response. +==================== =============================================================================================================== === + +Getting Weather Alerts +---------------------- +Can be used to get information on severe weather alerts along a specified route or a single car location. + +.. jupyter-execute:: + + import os + from here_location_services import LS + from geojson import Point + from datetime import datetime + + LS_API_KEY = os.environ.get("LS_API_KEY") + ls = LS(api_key=LS_API_KEY) + result = ls.get_weather_alerts( + geometry=Point(coordinates=[15.256, 23.456]), + start_time=datetime.now(), + width=3000, + ) + + print(result) + + +Attributes +---------- + +==================== =============================================================================================================== === +Attribute Type Doc +==================== =============================================================================================================== === +geometry Point or LineString or Polygon or MultiPolygon Point or LineString or Polygon or MultiPolygon defining the route or a single location +start_time :func:`datetime.datetime` Start time of the event +id str optional Unique weather alert id. +weather_severity :class:`WeatherSeverity ` optional Defines the severity of the weather event +weather_type :class:`WeatherType ` optional Defines the type of the weather event +country str optional String for ISO-3166-1 2-letter country code. +end_time :func:`datetime.datetime` optional End time of the event. If not present, warning is valid until it is not removed from the feed by national weather institutes (valid until warning is present in the response) +width int optional int. default 50000 +==================== =============================================================================================================== === diff --git a/docs/source/here_location_services.config.dest_weather_config.rst b/docs/source/here_location_services.config.dest_weather_config.rst new file mode 100644 index 0000000..8ff1830 --- /dev/null +++ b/docs/source/here_location_services.config.dest_weather_config.rst @@ -0,0 +1,8 @@ +here\_location\_services.config.dest\_weather\_config module +============================================================ + +.. automodule:: here_location_services.config.dest_weather_config + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/here_location_services.destination_weather_api.rst b/docs/source/here_location_services.destination_weather_api.rst new file mode 100644 index 0000000..eec691d --- /dev/null +++ b/docs/source/here_location_services.destination_weather_api.rst @@ -0,0 +1,8 @@ +here\_location\_services.destination\_weather\_api module +========================================================= + +.. automodule:: here_location_services.destination_weather_api + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/here_location_services.rst b/docs/source/here_location_services.rst index afbf65e..040c57e 100644 --- a/docs/source/here_location_services.rst +++ b/docs/source/here_location_services.rst @@ -19,6 +19,7 @@ Submodules here_location_services.config.base_config here_location_services.config.autosuggest_config here_location_services.config.isoline_routing_config + here_location_services.config.dest_weather_config here_location_services.config.routing_config here_location_services.config.matrix_routing_config here_location_services.config.search_config.rst @@ -33,3 +34,4 @@ Submodules here_location_services.routing_api.rst here_location_services.utils here_location_services.platform + here_location_services.destination_weather_api.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 932e2c2..09a292a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -36,6 +36,12 @@ A Python client for `HERE Location Services`_. Routing Matrix Routing +.. toctree:: + :maxdepth: 1 + :caption: Destination Weather + + Destination Weather + .. toctree:: :maxdepth: 1 :caption: Reference Guide diff --git a/here_location_services/config/autosuggest_config.py b/here_location_services/config/autosuggest_config.py index e0a90cf..7916151 100644 --- a/here_location_services/config/autosuggest_config.py +++ b/here_location_services/config/autosuggest_config.py @@ -13,7 +13,7 @@ class SearchCircle: defined by its center and radius(in meters). """ - def __init__(self, lat: str, lng: str, radius: int): + def __init__(self, lat: float, lng: float, radius: int): self.lat = lat self.lng = lng self.radius = radius diff --git a/here_location_services/config/dest_weather_config.py b/here_location_services/config/dest_weather_config.py new file mode 100644 index 0000000..93e9cbe --- /dev/null +++ b/here_location_services/config/dest_weather_config.py @@ -0,0 +1,146 @@ +# Copyright (C) 2019-2021 HERE Europe B.V. +# SPDX-License-Identifier: Apache-2.0 + +"""This module defines all the configs which will be required as inputs to + Destination Weather API.""" + +from .base_config import Bunch + + +class DestWeatherProduct(Bunch): + """A class to define constant attributes for ``product`` parameter identifying the + type of report to obtain. + + ``observation``: + current weather conditions from the eight closest locations to the specified location + + ``forecast_7days``: + morning, afternoon, evening and night weather forecasts for the next seven days. + + ``forecast_7days_simple``: + daily weather forecasts for the next seven days + + ``forecast_hourly``: + hourly weather forecasts for the next seven days + + ``alerts``: + forecasted weather alerts for the next 24 hours + + ``nws_alerts``: + all active watches and warnings for the US and Canada + """ + + +product = { + "observation": "observation", + "forecast7days": "forecast7days", + "forecast7daysSimple": "forecast7daysSimple", + "forecastHourly": "forecastHourly", + "alerts": "alerts", + "nwsAlerts": "nwsAlerts", +} + +#: Use this config for ``products``` of Destination Weather API. +#: Example: for ``forecastHourly`` product use ``DEST_WEATHER_PRODUCT.forecastHourly``. +DEST_WEATHER_PRODUCT = DestWeatherProduct(**product) + + +class DestWeatherUnits(Bunch): + """A class to define constant attributes for ``units`` parameter identifying + units of measurement used. + + ``metric``: + Follow metric system of measurements. Default. + + ``imperial``: + Follow imperial system of measurements + + """ + + +units = { + "metric": "metric", + "imperial": "imperial", +} + +#: Use this config for ``units``` of Destination Weather API. +#: Example: for ``metric`` units use ``DEST_WEATHER_UNITS.metric``. +DEST_WEATHER_UNITS = DestWeatherUnits(**units) + + +class WeatherSeverity(Bunch): + """A class to define the severity of the weather event + + ``insignificant``: + Event doesn't have significance by nature + + ``no_alerts``: + There are no alerts for the location + + ``minor``: + Minor Severity, the event is potentially dangerous but not usual + + ``medium``: + Medium Severity, the event is dangerous + + ``high``: + High Severity, The event is very dangerous + + ``emergency``: + Emergency. Take immediate action to protect life. + """ + + +weather_severity = { + "insignificant": 0, + "no_alerts": 1, + "minor": 2, + "medium": 3, + "high": 4, + "emergency": 5, +} + +#: Use this config for ``weather_severity``` of get weather alerts endpoint. +#: Example: for ``high severity`` events use ``WEATHER_SEVERITY.high``. +WEATHER_SEVERITY = WeatherSeverity(**weather_severity) + + +class WeatherType(Bunch): + """A class to define the type of the weather event""" + + +weather_type = { + "extremely_high_temperature": 1, + "extremely_low_temperature": 2, + "fog": 3, + "ice": 4, + "rain": 5, + "snow": 6, + "thunderstorm": 7, + "wind": 8, + "air_quality": 9, + "volcanic_ashfall": 10, + "avalanche": 11, + "tsunami": 12, + "dust_storm": 13, + "earthquake": 14, + "fire_danger": 15, + "flood": 16, + "high_waves": 17, + "gigh_uv_index": 18, + "low_water": 19, + "smoke": 20, + "volcano": 21, + "ice_in_waterway": 22, + "coastal_event": 23, + "civil_danger": 24, + "evacuation": 25, + "hazardous_material": 26, + "radiological_hazard": 27, + "shelter_in_place": 28, + "warning": 29, +} + +#: Use this config for ``weather_type``` of get weather alerts endpoint of Destination Weather API. +#: Example: for ``fog`` weather type use ``WEATHER_TYPE.fog``. +WEATHER_TYPE = WeatherType(**weather_type) diff --git a/here_location_services/destination_weather_api.py b/here_location_services/destination_weather_api.py new file mode 100644 index 0000000..eb33b77 --- /dev/null +++ b/here_location_services/destination_weather_api.py @@ -0,0 +1,162 @@ +# Copyright (C) 2019-2021 HERE Europe B.V. +# SPDX-License-Identifier: Apache-2.0 + +"""This module contains classes for accessing `HERE Destination Weather API `_. +""" # noqa E501 + +import time +from datetime import date, datetime +from typing import Any, Dict, List, Optional, Union + +from geojson import Feature, FeatureCollection, LineString, MultiPolygon, Point, Polygon + +from here_location_services.platform.auth import Auth + +from .apis import Api +from .exceptions import ApiError + + +class DestinationWeatherApi(Api): + """A class for accessing HERE routing APIs.""" + + def __init__( + self, + api_key: Optional[str] = None, + auth: Optional[Auth] = None, + proxies: Optional[dict] = None, + country: str = "row", + ): + super().__init__(api_key, auth=auth, proxies=proxies, country=country) + self._base_url = f"https://weather.{self._get_url_string()}" + + def get_dest_weather( + self, + products: List[str], + at: Optional[List] = None, + query: Optional[str] = None, + zipcode: Optional[str] = None, + hourly_date: Optional[Union[date, datetime]] = None, + one_observation: Optional[bool] = None, + language: Optional[str] = None, + units: Optional[str] = None, + ): + """Retrieves weather reports, weather forecasts, severe weather alerts and moon and sun rise and set information. + + See further information `Here Destination Weather API _`. + + :param products: List of :class:`DestWeatherProduct` identifying the type of + report to obtain. + :param at: A list of ``latitude`` and ``longitude`` specifying the area covered + by the weather report. + :param query: Free text query. Examples: "125, Berliner, berlin", "Beacon, Boston" + :param zipcode: ZIP code of the location. This parameter is supported only for locations in + the United States of America. + :param hourly_date: Date for which hourly forecasts are to be retrieved. Can be either a `date` or + `datetime` object + :param one_observation: Boolean, if set to true, the response only includes the closest + location. Only available when the `product` parameter is set to + `DEST_WEATHER_PRODUCT.observation`. + :param language: Defines the language used in the descriptions in the response. + :param units: Defines whether units or imperial units are used in the response. + :return: :class:`requests.Response` object. + :raises ApiError: If ``status_code`` of API response is not 200. + """ # noqa E501 + + path = "v3/report" + url = f"{self._base_url}/{path}" + params: Dict[str, str] = { + "products": ",".join([str(i) for i in products]), + } + if at: + params["location"] = ",".join([str(i) for i in at]) + if query: + params["q"] = query + if zipcode: + params["zipCode"] = zipcode + if hourly_date: + if type(hourly_date) is datetime.date: + params["hourlyDate"] = hourly_date.strftime("%Y-%m-%d") + else: + params["hourlyDate"] = hourly_date.strftime("%Y-%m-%dT%H:%M:%S") + if one_observation: + params["oneObservation"] = "true" if one_observation else "false" + if language: + params["lang"] = language + if units: + params["units"] = units + + resp = self.get(url, params=params, proxies=self.proxies) + if resp.status_code == 200: + return resp + else: + raise ApiError(resp) + + def get_weather_alerts( + self, + geometry: Union[Point, LineString, Polygon, MultiPolygon], + start_time: datetime, + id: Optional[str] = None, + weather_severity: Optional[int] = None, + weather_type: Optional[str] = None, + country: Optional[str] = None, + end_time: Optional[datetime] = None, + width: Optional[int] = 50000, + ): + """Retrieves weather reports, weather forecasts, severe weather alerts and moon and sun rise and set information. + + See further information `Here Destination Weather API _`. + + :param geometry: Point or LineString or Polygon or MultiPolygon defining the route or + a single location + :param start_time: Start time of the event + :param id: Unique weather alert id. + :param weather_severity: Defines the severity of the weather event as defined + in :class:`WeatherSeverity`. + :param weather_type: Defines the type of the weather event as defined + in :class:`WeatherType`. + :param country: String for ISO-3166-1 2-letter country code. + :param end_time: End time of the event. If not present, warning is valid until + it is not removed from the feed by national weather institutes + (valid until warning is present in the response) + :param width: int. default 50000 + :return: :class:`requests.Response` object. + :raises ApiError: If ``status_code`` of API response is not 200. + """ # noqa E501 + + path = "v3/alerts" + url = f"{self._base_url}/{path}" + + properties: Dict[str, Any] = { + "width": width, + } + weather_warnings: Dict[str, Any] = { + "startTime": time.mktime(start_time.timetuple()), + } + + if weather_severity: + weather_warnings["severity"] = weather_severity + if weather_type: + weather_warnings["type"] = weather_type + if country: + weather_warnings["country"] = country + if end_time: + weather_warnings["endTime"] = time.mktime(end_time.timetuple()) + + properties["warnings"] = [weather_warnings] + + f = Feature( + geometry=geometry, + properties=properties, + ) + + if id: + f.id = id + + feature_collection = FeatureCollection([]) + feature_collection.features.append(f) + + resp = self.post(url, data=feature_collection) + if resp.status_code == 200: + return resp + else: + raise ApiError(resp) diff --git a/here_location_services/ls.py b/here_location_services/ls.py index 224421d..0aecb64 100644 --- a/here_location_services/ls.py +++ b/here_location_services/ls.py @@ -6,10 +6,12 @@ import os import urllib import urllib.request -from datetime import datetime +from datetime import date, datetime from time import sleep from typing import Dict, List, Optional, Tuple, Union +from geojson import LineString, MultiPolygon, Point, Polygon + from here_location_services.config.routing_config import Scooter, Via from here_location_services.platform.apis.aaa_oauth2_api import AAAOauth2Api from here_location_services.platform.auth import Auth @@ -26,6 +28,7 @@ PolygonRegion, WorldRegion, ) +from .destination_weather_api import DestinationWeatherApi from .exceptions import ApiError from .geocoding_search_api import GeocodingSearchApi from .isoline_routing_api import IsolineRoutingApi @@ -33,6 +36,7 @@ from .responses import ( AutosuggestResponse, BrowseResponse, + DestinationWeatherResponse, DiscoverResponse, GeocoderResponse, IsolineResponse, @@ -94,6 +98,12 @@ def __init__( proxies=proxies, country=country, ) + self.destination_weather_api = DestinationWeatherApi( + api_key=api_key, + auth=self.auth, + proxies=proxies, + country=country, + ) def geocode(self, query: str, limit: int = 20, lang: str = "en-US") -> GeocoderResponse: """Calculate coordinates as result of geocoding for the given ``query``. @@ -306,6 +316,104 @@ def autosuggest( return response + def get_dest_weather( + self, + products: List[str], + at: Optional[List] = None, + query: Optional[str] = None, + zipcode: Optional[str] = None, + hourly_date: Optional[Union[date, datetime]] = None, + one_observation: Optional[bool] = None, + language: Optional[str] = None, + units: Optional[str] = None, + ) -> DestinationWeatherResponse: + """Retrieves weather reports, weather forecasts, severe weather alerts + and moon and sun rise and set information. + + :param products: List of :class:`DestWeatherProduct` identifying the type of + report to obtain. + :param at: A list of ``latitude`` and ``longitude`` specifying the area covered + by the weather report. + :param query: Free text query. Examples: "125, Berliner, berlin", "Beacon, Boston" + :param zipcode: ZIP code of the location. This parameter is supported only for locations in + the United States of America. + :param hourly_date: Date for which hourly forecasts are to be retrieved. Can be either a + `date` or `datetime` object + :param one_observation: Boolean, if set to true, the response only includes the closest + location. Only available when the `product` parameter is set to + `DEST_WEATHER_PRODUCT.observation`. + :param language: Defines the language used in the descriptions in the response. + :param units: Defines whether units or imperial units are used in the response. + :raises ValueError: If neither `at`, `query` or `zipcode` are passed. + :raises ValueError: If `one_observation` is set to true without passing + DEST_WEATHER_PRODUCT.observation in `products` + :return: :class:`DestinationWeatherResponse` object. + """ + + if at is None and query is None and zipcode is None: + raise ValueError("please provide either `at` or `query` or `zipcode`.") + if "observation" not in products and one_observation: + raise ValueError( + "`one_observation` can only be set when the `products` parameter " + + "is set to DEST_WEATHER_PRODUCT.observation" + ) + + resp = self.destination_weather_api.get_dest_weather( + products=products, + at=at, + query=query, + zipcode=zipcode, + hourly_date=hourly_date, + one_observation=one_observation, + language=language, + units=units, + ) + response = DestinationWeatherResponse.new(resp.json()) + return response + + def get_weather_alerts( + self, + geometry: Union[Point, LineString, Polygon, MultiPolygon], + start_time: datetime, + id: Optional[str] = None, + weather_severity: Optional[int] = None, + weather_type: Optional[str] = None, + country: Optional[str] = None, + end_time: Optional[datetime] = None, + width: Optional[int] = 50000, + ) -> DestinationWeatherResponse: + """Retrieves weather reports, weather forecasts, severe weather alerts + and moon and sun rise and set information. + + :param geometry: Point or LineString or Polygon or MultiPolygon defining the route or + a single location + :param start_time: Start time of the event + :param id: Unique weather alert id. + :param weather_severity: Defines the severity of the weather event as defined + in :class:`WeatherSeverity`. + :param weather_type: Defines the type of the weather event as defined + in :class:`WeatherType`. + :param country: String for ISO-3166-1 2-letter country code. + :param end_time: End time of the event. If not present, warning is valid until + it is not removed from the feed by national weather institutes + (valid until warning is present in the response) + :param width: int. default 50000 + :return: :class:`DestinationWeatherResponse` object. + """ + + resp = self.destination_weather_api.get_weather_alerts( + geometry=geometry, + id=id, + weather_severity=weather_severity, + weather_type=weather_type, + country=country, + start_time=start_time, + end_time=end_time, + width=width, + ) + response = DestinationWeatherResponse.new(resp.json()) + return response + def discover( self, query: str, diff --git a/here_location_services/responses.py b/here_location_services/responses.py index 161af4c..08b6313 100644 --- a/here_location_services/responses.py +++ b/here_location_services/responses.py @@ -193,3 +193,13 @@ def __init__(self, **kwargs): } for param, default in self._filters.items(): setattr(self, param, kwargs.get(param, default)) + + +class DestinationWeatherResponse(ApiResponse): + """A class representing the Destination Weather API response data.""" + + def __init__(self, **kwargs): + super().__init__() + self._filters = {"places": None, "features": None} + for param, default in self._filters.items(): + setattr(self, param, kwargs.get(param, default)) diff --git a/tests/conftest.py b/tests/conftest.py index 1850d8b..42a1fee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ import pytest from here_location_services.autosuggest_api import AutosuggestApi +from here_location_services.destination_weather_api import DestinationWeatherApi from here_location_services.exceptions import ConfigException from here_location_services.geocoding_search_api import GeocodingSearchApi from here_location_services.isoline_routing_api import IsolineRoutingApi @@ -51,6 +52,13 @@ def autosuggest_api(): return api +@pytest.fixture() +def destination_weather_api(): + """Create shared low level Location services Dest Weather instance as a pytest fixture.""" + api = DestinationWeatherApi(api_key=LS_API_KEY) + return api + + def env_setup_done(): """This function will return env variables required to authenticate APIs using auth token.""" try: diff --git a/tests/test_ls.py b/tests/test_ls.py index 8cafed3..6449c5f 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -2,12 +2,12 @@ # SPDX-License-Identifier: Apache-2.0 import json -from datetime import datetime +from datetime import datetime, timedelta import pandas as pd import pytest import pytz -from geojson import FeatureCollection +from geojson import FeatureCollection, Point from here_location_services import LS from here_location_services.config.autosuggest_config import POLITICAL_VIEW, SHOW, SearchCircle @@ -18,6 +18,12 @@ Truck, WayPointOptions, ) +from here_location_services.config.dest_weather_config import ( + DEST_WEATHER_PRODUCT, + DEST_WEATHER_UNITS, + WEATHER_SEVERITY, + WEATHER_TYPE, +) from here_location_services.config.isoline_routing_config import ( ISOLINE_ROUTING_AVOID_FEATURES, ISOLINE_ROUTING_TRANSPORT_MODE, @@ -52,6 +58,92 @@ LS_API_KEY = get_apikey() +@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") +def test_ls_weather_alerts(): + """Test weather alerts endpoint of destination weather api.""" + ls = LS(api_key=LS_API_KEY) + resp = ls.get_weather_alerts( + geometry=Point(coordinates=[15.256, 23.456]), + start_time=datetime.now(), + width=3000, + ) + assert resp + + resp2 = ls.get_weather_alerts( + geometry=Point(coordinates=[15.256, 23.456]), + start_time=datetime.now(), + weather_type=WEATHER_TYPE.ice, + weather_severity=WEATHER_SEVERITY.high, + country="US", + end_time=datetime.now() + timedelta(days=7), + ) + assert resp2 + + with pytest.raises(ApiError): + ls2 = LS(api_key="dummy") + ls2.get_weather_alerts( + geometry=Point(coordinates=[15.256, 23.456]), + start_time=datetime.now(), + ) + + +@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") +def test_ls_dest_weather(): + """Test destination weather api.""" + ls = LS(api_key=LS_API_KEY) + resp = ls.get_dest_weather(products=[DEST_WEATHER_PRODUCT.observation], query="Chicago") + assert resp.places + assert resp.places[0]["observations"] + + resp2 = ls.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.forecastHourly], + query="Chicago", + units=DEST_WEATHER_UNITS.imperial, + ) + assert resp2.places + assert resp2.places[0]["hourlyforecasts"] + + resp3 = ls.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.forecast7days], at=["-13.163068,-72.545128"] + ) + assert resp3.places + assert resp3.places[0]["extendedDailyforecasts"] + + resp4 = ls.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.forecast7daysSimple, DEST_WEATHER_PRODUCT.observation], + zipcode="10025", + one_observation=True, + ) + assert resp4.places + assert resp4.places[0]["observations"] + + resp5 = ls.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.forecast7daysSimple, DEST_WEATHER_PRODUCT.observation], + zipcode="10025", + at=["-13.163068,-72.545128"], + one_observation=True, + ) + assert resp5.places + assert resp5.places[0]["observations"] + + with pytest.raises(ValueError): + ls.get_dest_weather(products=[DEST_WEATHER_PRODUCT.forecast7days]) + + with pytest.raises(ValueError): + ls.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.forecast7days], query="Chicago", one_observation=True + ) + + with pytest.raises(ValueError): + ls.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.forecast7days], query="Chicago", one_observation=True + ) + + with pytest.raises(ApiError): + ls2 = LS(api_key="dummy") + ls2.get_dest_weather(products=[DEST_WEATHER_PRODUCT.observation], query="Chicago") + + @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_ls_autosuggest(): """Test autosuggest api.""" @@ -60,7 +152,7 @@ def test_ls_autosuggest(): assert resp.items assert len(resp.items) <= 5 - search_in_circle1 = SearchCircle(lat=52.53, lng="13.38", radius="10000") + search_in_circle1 = SearchCircle(lat=52.53, lng=13.38, radius="10000") search_in_bbox1 = ("13.08836", "52.33812", "13.761", "52.6755") resp3 = ls.autosuggest(query="bar", limit=5, search_in_circle=search_in_circle1, lang=["en"]) diff --git a/tests/test_ls_apis.py b/tests/test_ls_apis.py index d91b733..eddbbca 100644 --- a/tests/test_ls_apis.py +++ b/tests/test_ls_apis.py @@ -2,10 +2,13 @@ # SPDX-License-Identifier: Apache-2.0 from argparse import Namespace +from datetime import datetime import pytest import requests +from geojson import Point +from here_location_services.config.dest_weather_config import DEST_WEATHER_PRODUCT from here_location_services.config.isoline_routing_config import ( ISOLINE_ROUTING_TRANSPORT_MODE, RANGE_TYPE, @@ -18,6 +21,27 @@ LS_API_KEY = get_apikey() +@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") +def test_destination_weather(destination_weather_api): + """Test Destination Weather api.""" + resp = destination_weather_api.get_dest_weather( + products=[DEST_WEATHER_PRODUCT.observation], query="Chicago" + ) + assert type(resp) == requests.Response + assert resp.status_code == 200 + + +@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") +def test_weather_alerts(destination_weather_api): + """Test Destination Weather api.""" + resp = destination_weather_api.get_weather_alerts( + geometry=Point(coordinates=[15.256, 23.456]), + start_time=datetime.now(), + ) + assert type(resp) == requests.Response + assert resp.status_code == 200 + + @pytest.mark.skipif(not LS_API_KEY, reason="No api key found.") def test_autosuggest(autosuggest_api): """Test autosuggest api."""