diff --git a/assets/package-lock.json b/assets/package-lock.json index 980919dcd8291f0fe278d679325e39d0746468a6..6ee7c52bb48d003ee12298058696e0ac6bc11cf5 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -1,6 +1,6 @@ { "name": "waterdataui-assets", - "version": "0.49.0dev", + "version": "0.50.0dev", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/wdfn-server/waterdata/services/nwissite.py b/wdfn-server/waterdata/services/nwissite.py index 071e89c38fb2256223736dd181b37b98fac5f1bc..005e546e8de8d855b852c5cd96f34a5ac10b6043 100644 --- a/wdfn-server/waterdata/services/nwissite.py +++ b/wdfn-server/waterdata/services/nwissite.py @@ -3,117 +3,105 @@ Class and functions for calling NWIS services and working with the returned data. """ -from requests import exceptions as request_exceptions, Session from ..utils import parse_rdb from .. import app -class SiteService: +async def get(session, params): + # pylint: disable=no-member """ - Provides access to the NWIS site service + Returns a tuple containing the request status code and a list of dictionaries that represent the contents of + RDB file + :param session - instance aiohttp.ClientSession + :param dict params: + :yields + - status_code - status code returned from the service request + - reason - string + - site_data - list of dictionaries """ - - def __init__(self, endpoint): - """ - Constructor method. - - :param str endpoint: the scheme, host and path to the NWIS site service - """ - self.endpoint = endpoint - self.session = Session() - - def get(self, params): - # pylint: disable=no-member - """ - Returns a tuple containing the request status code and a list of dictionaries that represent the contents of - RDB file - - :param dict params: - :returns - - status_code - status code returned from the service request - - reason - string - - site_data - list of dictionaries - """ - app.logger.debug(f'Requesting data from {self.endpoint}') - default_params = { - 'format': 'rdb' - } - default_params.update(params) - try: - response = self.session.get(self.endpoint, params=default_params) - except (request_exceptions.Timeout, request_exceptions.ConnectionError) as err: - app.logger.error(repr(err)) - return 500, repr(err), None + endpoint = app.config["SITE_DATA_ENDPOINT"] + app.logger.debug(f'Requesting data from {endpoint}') + default_params = { + 'format': 'rdb' + } + default_params.update(params) + async with session.get(endpoint, params=default_params) as response: if response.status_code == 200: - return 200, response.reason, list(parse_rdb(response.iter_lines(decode_unicode=True))) + async with response.iter_lines(decode_unicode=True) as lines: + return 200, response.reason, list(parse_rdb(lines)) return response.status_code, response.reason, [] - def get_site_data(self, site_no, agency_cd=''): - """ - Get the metadata for site_no, agency_cd (which may be blank) using the additional query parameters, param - Note that more than one dictionary can be returned if agency_cd is empty. - :param str site_no: site identifier - :param str agency_cd: identifier for the agency that owns the site - :returns: - - status - status code from response - - reason - string - - site_metadata - list of dict representing the data returned in the rdb file - """ - params = { - 'sites': site_no, - 'siteOutput': 'expanded' - } - if agency_cd: - params['agencyCd'] = agency_cd - return self.get(params) - def get_period_of_record(self, site_no, agency_cd=''): - """ - Get the parameters measured at the site(s). +async def get_site_data(session, site_no, agency_cd=''): + """ + Get the metadata for site_no, agency_cd (which may be blank) using the additional query parameters, param + Note that more than one dictionary can be returned if agency_cd is empty. + :param session: instance of aiohttp.ClientSession + :param str site_no: site identifier + :param str agency_cd: identifier for the agency that owns the site + :yields: + - status - status code from response + - reason - string + - site_metadata - list of dict representing the data returned in the rdb file + """ + params = { + 'sites': site_no, + 'siteOutput': 'expanded' + } + if agency_cd: + params['agencyCd'] = agency_cd + return await get(session, params) + - :param str site_no: site identifier - :param str agency_cd: identifier for the agency that owns the site: - :returns: - - status - status code from response - - reason - string - - periodOfRecord - list of dict representing the period of record for the data available at the site - """ - params = { - 'sites': site_no, - 'seriesCatalogOutput': True, - 'siteStatus': 'all' - } - if agency_cd: - params['agencyCd'] = agency_cd - return self.get(params) +async def get_period_of_record(session, site_no, agency_cd=''): + """ + Get the parameters measured at the site(s). + :param session: instance of aiohttp.ClientSession + :param str site_no: site identifier + :param str agency_cd: identifier for the agency that owns the site: + :returns: + - status - status code from response + - reason - string + - periodOfRecord - list of dict representing the period of record for the data available at the site + """ + params = { + 'sites': site_no, + 'seriesCatalogOutput': True, + 'siteStatus': 'all' + } + if agency_cd: + params['agencyCd'] = agency_cd + return await get(session, params) - def get_huc_sites(self, huc_cd): - """ - Get all sites within a hydrologic unit as identified by its - hydrologic unit code (HUC). - :param str huc_cd: hydrologic unit code - :returns: all sites in the specified HUC - - status - status code from response - - reason - string - - sites - list of dict representing the sites in huc_cd - """ - return self.get({ - 'huc': huc_cd - }) +async def get_huc_sites(session, huc_cd): + """ + Get all sites within a hydrologic unit as identified by its + hydrologic unit code (HUC). + :param session: instance of aiohttp.ClientSession + :param str huc_cd: hydrologic unit code + :returns: all sites in the specified HUC + - status - status code from response + - reason - string + - sites - list of dict representing the sites in huc_cd + """ + return await get(session, { + 'huc': huc_cd + }) - def get_county_sites(self, state_county_cd): - """ - Get all sites within a county. - :param str state_county_cd: FIPS ID for a statecounty - :returns: all sites in the specified county - - status - status code from response - - reason - string - - sites - list of dict representing the site in state_county_cd - """ - return self.get({ - 'countyCd': state_county_cd - }) +def get_county_sites(session, state_county_cd): + """ + Get all sites within a county. + :param session: instance of aiohttp.ClientSession + :param str state_county_cd: FIPS ID for a statecounty + :returns: all sites in the specified county + - status - status code from response + - reason - string + - sites - list of dict representing the site in state_county_cd + """ + return get(session, { + 'countyCd': state_county_cd + }) diff --git a/wdfn-server/waterdata/services/sifta.py b/wdfn-server/waterdata/services/sifta.py index e5067569f9b3a801358b9a66559b8aed0bf9d2e3..47f6ade3a4693e20f36c0b700ecbd9a54ed25825 100644 --- a/wdfn-server/waterdata/services/sifta.py +++ b/wdfn-server/waterdata/services/sifta.py @@ -1,33 +1,24 @@ """ Helpers to retrieve SIFTA cooperator data. """ -from requests import exceptions as request_exceptions, Session from .. import app -class SiftaService: - # pylint: disable=too-few-public-methods, no-member +async def get_cooperators(session, site_no): """ - Provide access to a service that returns cooperator data - """ - def __init__(self, endpoint): - self.endpoint = endpoint - self.session = Session() + Gets the cooperator data from the SIFTA service - def get_cooperators(self, site_no): - """ - Gets the cooperator data from the SIFTA service + :param session - instance aiohttp.ClientSession + :param str site_no: USGS site number + :yields Array of dict + """ + url = f'{app.config["COOPERATOR_SERVICE_ENDPOINT"]}{site_no}' + app.logger.debug(f'Requesting data from {url}') # pylint: disable=no-member - :param site_no: USGS site number - :return Array of dict - """ - url = f'{self.endpoint}{site_no}' - try: - response = self.session.get(url) - except (request_exceptions.Timeout, request_exceptions.ConnectionError) as err: - app.logger.error(repr(err)) - return [] + async with session.get(url) as response: + # TODO: Add exception handling + app.logger.debug(f'Retrieved data from {url}') # pylint: disable=no-member if response.status_code != 200: return [] diff --git a/wdfn-server/waterdata/views.py b/wdfn-server/waterdata/views.py index 46e322ad1a01e8e4167b1dcd34eb989f19891888..51f9b4490965590015902e1a1d55d17b9a5a8c87 100644 --- a/wdfn-server/waterdata/views.py +++ b/wdfn-server/waterdata/views.py @@ -1,6 +1,7 @@ """ Main application views. """ +import asyncio import datetime import json import smtplib @@ -16,17 +17,15 @@ from .utils import defined_when, set_cookie_for_banner_message, create_message from .services.camera import get_monitoring_location_camera_details from .services.nwissite import SiteService from .services.ogc import MonitoringLocationNetworkService -from .services.sifta import SiftaService +from .services.sifta import get_cooperators from .services.timezone import TimeZoneService # Station Fields Mapping to Descriptions from .constants import STATION_FIELDS_D -site_service = SiteService(app.config['SITE_DATA_ENDPOINT']) monitoring_location_network_service = \ MonitoringLocationNetworkService(app.config['MONITORING_LOCATIONS_OBSERVATIONS_ENDPOINT']) time_zone_service = TimeZoneService(app.config['WEATHER_SERVICE_ENDPOINT']) -sifta_service = SiftaService(app.config['COOPERATOR_SERVICE_ENDPOINT']) @app.context_processor @@ -98,18 +97,21 @@ def iv_data_availability(): """Render the IV data availability statement page.""" return render_template('iv_data_availability_statement.html') - @app.route('/monitoring-location/<site_no>/', methods=['GET']) -def monitoring_location(site_no): +async def monitoring_location(site_no): # pylint: disable=too-many-locals # TODO: refactor to use more utility functions so that this doesn't have too many locals # pylint: disable=fixme """ Monitoring Location view - :param site_no: USGS site number - """ agency_cd = request.args.get('agency_cd', '') + + async with aiohttp.ClientSession() + + loop = asyncio.get_event_loop() + coroutines = [get_cooperators(sifta_endpoint, site_no)] + results = loop.run_until_complete(asyncio.gather(*coroutines)) site_status, site_status_reason, site_data = site_service.get_site_data(site_no, agency_cd) json_ld = None