Coverage for patatmo/api/client.py : 59%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
#!/usr/bin/env python3 # system modules
# internal modules
# external modules
""" Netatmo api client
Args: authentication (Authentication, optional): the authentication used for Oauth2 authentication """
################## ### Properties ### ################## def logger(self): """ the logging.Logger used for logging. Defaults to logging.getLogger(__name__). """
def logger(self, logger): # pragma: no cover assert isinstance(logger, logging.Logger), \ "logger property has to be a logging.Logger" self._logger = logger
def authentication(self): # pragma: no cover """ The Authentication used for server authentication Defaults to an empty instance of Authentication on first use. """ try: # try to return the internal attribute return self._authentication except AttributeError: # didn't work # set it to new instance self._authentication = authentication.Authentication() # return internal attribute return self._authentication
def authentication(self, newauth): "authentication property needs to be of " "class Authentication. Using empty instance instead " "of {}.".format(newauth))
else:
############### ### Methods ### ############### full=False, resolution=0.06): """ Issue a Getpublicdata POST request to the netatmo server
Args: region (dict): dict definig the desired request region. Required keys: lat_ne [-85;85], lat_sw [-85;85], lon_ne [-180;180] and lon_sw [-180;180] with lat_ne > lat_sw and lon_ne > lon_sw required_data (str or None, optional): Defaults to None which means no filter. filter (bool, optional): server-side filter for stations with unusual data. Defaults to False with means no filter full (bool, optional): subdivide the requested region into smaller requests to get around the zoom levels present in the Getpublicdata API. This will take longer, obviously. resolution (float, optional): if full=True, specifies the maximum size of the lat/lon grid boxes used to subdivide the region in degrees. It seems like something like 0.06 degrees is the larges possible value to really get ALL stations. This is default.
Returns: instance of GetpublicdataResponse with response data
Raises: InvalidApiInputError or derivates if wrong input was provided ApiResponseError or derivates if api responded with error
Returns: GetpublicdataResponse: The api response """
### Check the input ### # check the region raise InvalidRegionError except BaseException: raise InvalidRegionError or region["lon_ne"] < region["lon_sw"]: raise InvalidRegionError # check the required_data option and required_data is not None: raise InvalidRequiredDataError # check the filter option raise InvalidApiInputError("'filter' needs to be bool")
nlon = floor((region["lon_ne"] - region["lon_sw"]) / resolution) nlat = floor((region["lat_ne"] - region["lat_sw"]) / resolution) nlat = max(nlat, 2) nlon = max(nlon, 2) lats = np.linspace(region["lat_sw"], region["lat_ne"], num=nlat,) lons = np.linspace(region["lon_sw"], region["lon_ne"], num=nlon,) num_requests = (nlon - 1) * (nlat - 1) responses = [] # start with empty list request_count = 0 for lat_sw, lat_ne, in zip(lats[:-1], lats[1:]): for lon_sw, lon_ne in zip(lons[:-1], lons[1:]): region_cur = {"lat_sw": lat_sw, "lat_ne": lat_ne, "lon_sw": lon_sw, "lon_ne": lon_ne} self.logger.info("current region: {}".format(region_cur)) self.logger.info("Request {} of {}...".format( request_count + 1, num_requests)) response_cur = self.Getpublicdata( region=region_cur, filter=filter, required_data=required_data, full=False) self.logger.info("Stations in this region: {}".format( response_cur.dataframe(only_inside=True).shape[0])) responses.append(response_cur) request_count += 1
return responsetypes.GetpublicdataMultiResponse( request=requests.GetpublicdataRequest(), # Fake request response={"parts": responses}, ) else: ### Create the payload ### # set the access token "access_token") # set the region # set the required_data payload["required_data"] = str(required_data) # set the filter option
### issue the request ###
### check for errors ### raise API_ERRORS.get(error, ApiResponseError(error))
scale=None, date_begin=None, date_end=None, real_time=False, optimize=False, full=False): """ Issue a Getmeasure POST request to the netatmo server Taken from API documentation: https://dev.netatmo.com/dev/resources/technical/reference/common/getmeasure
Args: device_id (str): The mac address of the device module_id (str, optinal): The mac address of the module of interest. If not specified, returns data of the device. If specified, returns data from the specified module type (list of str, optional): Measures interested in. List of "rain","humidity","pressure","wind" and "temperature". scale (str, optional): Timelapse between two measurements. "max" (every value is returned), "30min" (1 value every 30min), "1hour", "3hours", "1day", "1week", "1month". Defaults to "max". date_begin,date_end (int, optional): UNIX-timestamp of first/last measure to receive. Limit is 1024 measures. optimize (bool, optional): Determines the format of the answer. Default is False. For mobile apps we recommend True and False if bandwidth isn't an issue as it is easier to parse. real_time (bool, optional): If scale different than max, timestamps are by default offset + scale/2. To get exact timestamps, use true. Default is false. full (bool, optional): Make sure the full requested time range is returned by requesting multiple times. Default is ``False``.
Returns: GetmeasureResponse: The api response """ ### Check the input ### # check device_id raise InvalidApiInputError("device_id = '{}' doesn't look like " "MAC address".format(device_id)) # check module_id raise InvalidApiInputError( "module_id = '{}' doesn't look like " "MAC address".format(module_id)) # check scale if scale not in GETMEASURE_ALLOWED_TYPES_BY_SCALE.keys(): raise InvalidApiInputError("scale must be one of {}".format( list(GETMEASURE_ALLOWED_TYPES_BY_SCALE.keys()))) else: # not specified # check type if not all([x in allowed_types for x in type]): raise InvalidApiInputError( "at given scale '{}' type must be a " "sublist of {}".format( scale, allowed_types)) else: # not specified # check date_begin raise Exception self.logger.warning( "Specified date_begin is in the future!") except BaseException: raise InvalidApiInputError("date_begin must be UNIX-timestamp") raise Exception except BaseException: raise InvalidApiInputError("date_end must be UNIX-timestamp") raise InvalidApiInputError("date_end must be greater than " "date_begin") # check the optimize option raise InvalidApiInputError("optimize must be bool") # check real_time option raise InvalidApiInputError("real_time must be bool")
### Prepare the payload ### # access token "access_token") # device id # module id # scale # type # date_begin # date_end # optimize # real_time
### issue the request ### responses = [] last_max_time = payload["date_begin"] full_time_range = payload["date_end"] - payload["date_begin"] n_requests = 0 while True: cur_payload = payload.copy() cur_payload.update({"date_begin":last_max_time}) self.logger.info("Issuing Getmeasure request Nr. {}...".format( n_requests+1)) n_requests += 1 apirequest = requests.GetmeasureRequest(payload=cur_payload) apiresponse = apirequest.response cur_times = \ [float(t) for t in apiresponse.response["body"].keys()] cur_max_time = max(cur_times) if cur_max_time >= payload["date_end"] \ or cur_max_time == last_max_time: self.logger.info("Request Nr. {} didn't introduce " "anything new. Stop.".format(n_requests)) break else: self.logger.info("Request Nr. {:3d} fills up to {:5.1f}% of " "the requested time range".format(n_requests, (cur_max_time-payload["date_begin"]) / full_time_range*100)) responses.append(apiresponse) last_max_time = cur_max_time return responsetypes.GetmeasureMultiResponse( request=requests.GetmeasureRequest(), # Fake request response={"parts": responses}, ) else:
### check for errors ### raise API_ERRORS.get(error, ApiResponseError(error))
""" Issue a Getstationsdata POST request to the netatmo server Taken from API documentation:
Args: device_id (str): The mac address of the device get_favourites (bool): To retrieve user's favorite weather stations. Is converted to bool. Default is false.
Returns: GetstationsdataResponse: The api response """ ### Check the input ### # check device_id raise InvalidApiInputError("device_id = '{}' doesn't look like " "MAC address".format(device_id)) # check get_favourites except BaseException: raise InvalidApiInputError("get_favourites has to be bool or at " "least convertible to bool")
### create the payload ### # access token "access_token") # device id # get_favourites
### issue the request ###
### check for errors ### raise API_ERRORS.get(error, ApiResponseError(error))
def __repr__(self): # pragma: no cover """ python representation of this object """ # self.logger.debug("__repr__ called") reprstring = ( "{classname}(\n" "authentication = {authentication}\n" ")").format( classname="{module}.{name}".format( name=self.__class__.__name__, module=self.__class__.__module__), authentication=self.authentication.__repr__()) return reprstring |