Пример #1
0
 def fetch(self,
           station: str = None,
           lat: float = None,
           lon: float = None) -> str:
     """
     Fetches a report string from the service
     """
     if station:
         valid_station(station)
     elif lat is None or lon is None:
         raise ValueError('No valid fetch parameters')
     try:
         url, params = self._make_url(station, lat, lon)
         url += '?' + urlencode(params)
         # Non-null data signals a POST request
         data = {} if self.method == 'POST' else None
         resp = request.urlopen(url, data=data, timeout=10)
         if resp.status != 200:
             raise SourceError(
                 f'{self.__class__.__name__} server returned {resp.status}')
     except URLError:
         raise ConnectionError(
             f'Unable to connect to {self.__class__.__name__} server')
     report = self._extract(resp.read().decode('utf-8'), station)
     # This split join replaces all *whitespace elements with a single space
     if isinstance(report, list):
         return [' '.join(r.split()) for r in report]
     return ' '.join(report.split())
Пример #2
0
 def fetch(self, station: str) -> str:
     """
     Fetches a report string from the service
     """
     valid_station(station)
     resp = requests.get(self.url.format(self.rtype, station)).text
     return self._extract(resp)
Пример #3
0
 async def async_fetch(self,
                       station: str = None,
                       lat: float = None,
                       lon: float = None) -> str:
     """
     Asynchronously fetch a report string from the service
     """
     if station:
         valid_station(station)
     elif lat is None or lon is None:
         raise ValueError('No valid fetch parameters')
     url, params = self._make_url(station, lat, lon)
     try:
         async with aiohttp.ClientSession(timeout=_atimeout) as sess:
             async with getattr(sess,
                                self.method.lower())(url,
                                                     params=params) as resp:
                 if resp.status != 200:
                     raise SourceError(
                         f'{self.__class__.__name__} server returned {resp.status}'
                     )
                 text = await resp.text()
     except aiohttp.ClientConnectionError:
         raise ConnectionError(
             f'Unable to connect to {self.__class__.__name__} server')
     report = self._extract(text, station)
     # This split join replaces all *whitespace elements with a single space
     if isinstance(report, list):
         return [' '.join(r.split()) for r in report]
     return ' '.join(report.split())
Пример #4
0
def parse(station: str, report: str) -> (MetarData, Units):
    """
    Returns MetarData and Units dataclasses with parsed data and their associated units
    """
    core.valid_station(station)
    if not report:
        return None, None
    return parse_na(report) if core.uses_na_format(
        station[:2]) else parse_in(report)
Пример #5
0
    def __init__(self, station: str):
        # Raises a BadStation error if needed
        valid_station(station)

        #: Service object used to fetch the report string
        self.service = service.get_service(station)(self.__class__.__name__.lower())
        
        #: 4-character ICAO station ident code the report was initialized with
        self.station = station
Пример #6
0
    def __init__(self, station: str):
        # Raises a BadStation error if needed
        valid_station(station)

        #: Service object used to fetch the report string
        self.service = service.get_service(station)(
            self.__class__.__name__.lower())

        #: 4-character ICAO station ident code the report was initialized with
        self.station = station
Пример #7
0
def parse(station: str, txt: str) -> {str: object}:
    """Returns a dictionary of parsed METAR data

    Keys: Station, Time, Wind-Direction, Wind-Speed, Wind-Gust, Wind-Variable-Dir,
          Visibility, Runway-Vis-List, Altimeter, Temperature, Dewpoint,
          Cloud-List, Other-List, Remarks, Units

    Units is dict of identified units of measurement for each field
    """
    core.valid_station(station)
    return parse_na(txt) if core.uses_na_format(station[:2]) else parse_in(txt)
Пример #8
0
 def test_valid_station(self):
     """
     While not designed to catch all non-existant station idents,
     valid_station should catch non-ICAO strings and filter based on known prefixes
     """
     # Good stations
     for station in ('KJFK', 'K123', 'EIGL', 'PHNL', 'MNAH'):
         core.valid_station(station)
     # Bad stations
     for station in ('12K', 'MAYT'):
         with self.assertRaises(exceptions.BadStation):
             core.valid_station(station)
Пример #9
0
 def test_valid_station(self):
     """
     While not designed to catch all non-existant station idents,
     valid_station should catch non-ICAO strings and filter based on known prefixes
     """
     # Good stations
     for station in ('KJFK', 'K123', 'EIGL', 'PHNL', 'MNAH'):
         core.valid_station(station)
     # Bad stations
     for station in ('12K', 'MAYT'):
         with self.assertRaises(exceptions.BadStation):
             core.valid_station(station)
Пример #10
0
def parse(station: str, report: str) -> TafData:
    """
    Returns TafData and Units dataclasses with parsed data and their associated units
    """
    if not report:
        return None, None
    core.valid_station(station)
    while len(report) > 3 and report[:4] in ('TAF ', 'AMD ', 'COR '):
        report = report[4:]
    _, station, time = core.get_station_and_time(report[:20].split())
    retwx = {
        'end_time': None,
        'raw': report,
        'remarks': None,
        'start_time': None,
        'station': station,
        'time': core.make_timestamp(time)
    }
    report = report.replace(station, '')
    report = report.replace(time, '').strip()
    if core.uses_na_format(station):
        use_na = True
        units = Units(**NA_UNITS)
    else:
        use_na = False
        units = Units(**IN_UNITS)
    # Find and remove remarks
    report, retwx['remarks'] = core.get_taf_remarks(report)
    # Split and parse each line
    lines = core.split_taf(report)
    parsed_lines = parse_lines(lines, units, use_na)
    # Perform additional info extract and corrections
    if parsed_lines:
        parsed_lines[-1]['other'], retwx['max_temp'], retwx['min_temp'] \
            = core.get_temp_min_and_max(parsed_lines[-1]['other'])
        if not (retwx['max_temp'] or retwx['min_temp']):
            parsed_lines[0]['other'], retwx['max_temp'], retwx['min_temp'] \
                = core.get_temp_min_and_max(parsed_lines[0]['other'])
        # Set start and end times based on the first line
        start, end = parsed_lines[0]['start_time'], parsed_lines[0]['end_time']
        parsed_lines[0]['end_time'] = None
        retwx['start_time'], retwx['end_time'] = start, end
        parsed_lines = core.find_missing_taf_times(parsed_lines, start, end)
        parsed_lines = core.get_taf_flight_rules(parsed_lines)
    # Extract Oceania-specific data
    if retwx['station'][0] == 'A':
        parsed_lines[-1]['other'], retwx['alts'], retwx['temps'] \
            = core.get_oceania_temp_and_alt(parsed_lines[-1]['other'])
    # Convert to dataclass
    retwx['forecast'] = [TafLineData(**line) for line in parsed_lines]
    return TafData(**retwx), units
Пример #11
0
def parse(station: str, report: str) -> TafData:
    """
    Returns TafData and Units dataclasses with parsed data and their associated units
    """
    if not report:
        return None, None
    core.valid_station(station)
    while len(report) > 3 and report[:4] in ('TAF ', 'AMD ', 'COR '):
        report = report[4:]
    _, station, time = core.get_station_and_time(report[:20].split())
    retwx = {
        'end_time': None,
        'raw': report,
        'remarks': None,
        'start_time': None,
        'station': station,
        'time': core.make_timestamp(time)
    }
    report = report.replace(station, '')
    report = report.replace(time, '').strip()
    if core.uses_na_format(station):
        use_na = True
        units = Units(**NA_UNITS)
    else:
        use_na = False
        units = Units(**IN_UNITS)
    # Find and remove remarks
    report, retwx['remarks'] = core.get_taf_remarks(report)
    # Split and parse each line
    lines = core.split_taf(report)
    parsed_lines = parse_lines(lines, units, use_na)
    # Perform additional info extract and corrections
    if parsed_lines:
        parsed_lines[-1]['other'], retwx['max_temp'], retwx['min_temp'] \
            = core.get_temp_min_and_max(parsed_lines[-1]['other'])
        if not (retwx['max_temp'] or retwx['min_temp']):
            parsed_lines[0]['other'], retwx['max_temp'], retwx['min_temp'] \
                = core.get_temp_min_and_max(parsed_lines[0]['other'])
        # Set start and end times based on the first line
        start, end = parsed_lines[0]['start_time'], parsed_lines[0]['end_time']
        parsed_lines[0]['end_time'] = None
        retwx['start_time'], retwx['end_time'] = start, end
        parsed_lines = core.find_missing_taf_times(parsed_lines, start, end)
        parsed_lines = core.get_taf_flight_rules(parsed_lines)
    # Extract Oceania-specific data
    if retwx['station'][0] == 'A':
        parsed_lines[-1]['other'], retwx['alts'], retwx['temps'] \
            = core.get_oceania_temp_and_alt(parsed_lines[-1]['other'])
    # Convert to dataclass
    retwx['forecast'] = [TafLineData(**line) for line in parsed_lines]
    return TafData(**retwx), units
Пример #12
0
 def fetch(self, station: str) -> str:
     """
     Fetches a report string from the service
     """
     valid_station(station)
     try:
         resp = getattr(requests, self.method.lower())(self.url.format(self.rtype, station))
         if resp.status_code != 200:
             raise SourceError(f'{self.__class__.__name__} server returned {resp.status_code}')
     except requests.exceptions.ConnectionError:
         raise ConnectionError(f'Unable to connect to {self.__class__.__name__} server')
     report = self._extract(resp.text, station)
     # This split join replaces all *whitespace elements with a single space
     return ' '.join(report.split())
Пример #13
0
 def fetch(self, station: str) -> str:
     """
     Fetches a report string from the service
     """
     valid_station(station)
     try:
         resp = requests.get(self.url.format(self.rtype, station))
         if resp.status_code != 200:
             raise SourceError(
                 f'{self.__class__.__name__} server returned {resp.status_code}'
             )
     except requests.exceptions.ConnectionError:
         raise ConnectionError(
             f'Unable to connect to {self.__class__.__name__} server')
     return self._extract(resp.text)
Пример #14
0
def MultiStation(stations: str) -> [str]:
    """
    Validates a comma-separated list of station idents
    """
    stations = stations.upper().split(",")
    if not stations:
        raise Invalid("Could not find any stations in the request")
    if len(stations) > 10:
        raise Invalid("Multi requests are limited to 10 stations or less")
    for station in stations:
        try:
            valid_station(station)
        except BadStation:
            raise Invalid(f"{station} is not a valid ICAO station ident")
    return stations
Пример #15
0
def Location(loc: str) -> [str]:
    """Validates a station ident or coordinate pair string"""
    loc = loc.split(',')
    if len(loc) == 1:
        try:
            valid_station(loc[0])
        except BadStation:
            raise Invalid(f'{loc[0]} is not a valid ICAO station ident')
    elif len(loc) == 2:
        try:
            float(loc[0])
            float(loc[1])
        except:
            raise Invalid(f'{loc} is not a valid coordinate pair')
    else:
        raise Invalid(f'{loc} is not a valid station/coordinate pair')
    return loc
Пример #16
0
 async def async_fetch(self, station: str = None, lat: float = None, lon: float = None) -> str:
     """
     Asynchronously fetch a report string from the service
     """
     if station:
         valid_station(station)
     elif lat is None or lon is None:
         raise ValueError('No valid fetch parameters')
     url, params = self._make_url(station, lat, lon)
     try:
         async with aiohttp.ClientSession(timeout=_atimeout) as sess:
             async with getattr(sess, self.method.lower())(url, params=params) as resp:
                 if resp.status != 200:
                     raise SourceError(f'{self.__class__.__name__} server returned {resp.status}')
                 text = await resp.text()
     except aiohttp.ClientConnectionError:
         raise ConnectionError(f'Unable to connect to {self.__class__.__name__} server')
     report = self._extract(text, station)
     # This split join replaces all *whitespace elements with a single space
     if isinstance(report, list):
         return [' '.join(r.split()) for r in report]
     return ' '.join(report.split())
Пример #17
0
 def fetch(self, station: str = None, lat: float = None, lon: float = None) -> str:
     """
     Fetches a report string from the service
     """
     if station:
         valid_station(station)
     elif lat is None or lon is None:
         raise ValueError('No valid fetch parameters')
     try:
         url, params = self._make_url(station, lat, lon)
         url += '?' + urlencode(params)
         # Non-null data signals a POST request
         data = {} if self.method == 'POST' else None
         resp = request.urlopen(url, data=data, timeout=10)
         if resp.status != 200:
             raise SourceError(f'{self.__class__.__name__} server returned {resp.status}')
     except URLError:
         raise ConnectionError(f'Unable to connect to {self.__class__.__name__} server')
     report = self._extract(resp.read().decode('utf-8'), station)
     # This split join replaces all *whitespace elements with a single space
     if isinstance(report, list):
         return [' '.join(r.split()) for r in report]
     return ' '.join(report.split())
Пример #18
0
def _root(item: str) -> dict:
    """
    Parses report root data including station and report type
    """
    items = item.split()
    rtype = None
    station = None
    # Find valid station
    for item in items:
        try:
            core.valid_station(item)
            station = item
            break
        except BadStation:
            continue
    # Determine report type
    if 'UA' in items:
        rtype = 'UA'
    elif 'UUA' in items:
        rtype = 'UUA'
    return {
        'station': station,
        'type': rtype,
    }
Пример #19
0
def _root(item: str) -> dict:
    """
    Parses report root data including station and report type
    """
    items = item.split()
    rtype = None
    station = None
    # Find valid station
    for item in items:
        try:
            core.valid_station(item)
            station = item
            break
        except BadStation:
            continue
    # Determine report type
    if 'UA' in items:
        rtype = 'UA'
    elif 'UUA' in items:
        rtype = 'UUA'
    return {
        'station': station,
        'type': rtype,
    }
Пример #20
0
def parse(station: str, txt: str) -> (MetarData, Units):
    """
    Returns MetarData and Units dataclasses with parsed data and their associated units
    """
    core.valid_station(station)
    return parse_na(txt) if core.uses_na_format(station[:2]) else parse_in(txt)
Пример #21
0
def parse(station: str,
          txt: str,
          delim: str = '<br/>&nbsp;&nbsp;') -> {
              str: object
          }:
    """
    Returns a dictionary of parsed TAF data

    'delim' is the divider between forecast lines. Ex: aviationweather.gov uses '<br/>&nbsp;&nbsp;'

    Keys: Station, Time, Forecast, Remarks, Min-Temp, Max-Temp, Units

    Oceania stations also have the following keys: Temp-List, Alt-List

    Forecast is list of report dicts in order of time with the following keys:
    Type, Start-Time, End-Time, Flight-Rules, Wind-Direction, Wind-Speed, Wind-Gust, Wind-Shear,
    Visibility, Altimeter, Cloud-List, Icing-List, Turb-List, Other-List, Probability, Raw-Line

    Units is dict of identified units of measurement for each field
    """
    core.valid_station(station)
    retwx = {}
    while len(txt) > 3 and txt[:4] in ['TAF ', 'AMD ', 'COR ']:
        txt = txt[4:]
    _, retwx['Station'], retwx['Time'] = core.get_station_and_time(
        txt[:20].split(' '))
    txt = txt.replace(retwx['Station'], '')
    txt = txt.replace(retwx['Time'], '')
    if core.uses_na_format(retwx['Station']):
        is_international = False
        units = copy(NA_UNITS)
    else:
        is_international = True
        units = copy(IN_UNITS)
    retwx['Remarks'] = ''
    parsed_lines = []
    prob = ''
    lines = txt.strip(' ').split(delim)
    while lines:
        line = lines[0].strip(' ')
        line = core.sanitize_line(line)
        #Remove Remarks from line
        index = core.find_first_in_list(line, TAF_RMK)
        if index != -1:
            retwx['Remarks'] = line[index:]
            line = line[:index].strip(' ')
        #Separate new lines fixed by sanitizeLine
        index = core.find_first_in_list(line, TAF_NEWLINE)
        if index != -1:
            lines.insert(1, line[index + 1:])
            line = line[:index]
        # Remove prob from the beginning of a line
        if line.startswith('PROB'):
            # Add standalone prob to next line
            if len(line) == 6:
                prob = line
                line = ''
            # Add to current line
            elif len(line) > 6:
                prob = line[:6]
                line = line[6:].strip()
        if line:
            # Separate full prob forecast into its own line
            if ' PROB' in line:
                probindex = line.index(' PROB')
                lines.insert(1, line[probindex + 1:])
                line = line[:probindex]
            raw_line = prob + ' ' + line if prob else line
            parsed_line, units = parse_in_line(line, units) if is_international \
                else parse_na_line(line, units)
            parsed_line['Probability'] = prob[4:]
            parsed_line['Raw-Line'] = raw_line
            prob = ''
            parsed_lines.append(parsed_line)
        lines.pop(0)
    if parsed_lines:
        parsed_lines[len(parsed_lines) - 1]['Other-List'], retwx['Max-Temp'], retwx['Min-Temp'] \
            = core.get_temp_min_and_max(parsed_lines[len(parsed_lines) - 1]['Other-List'])
        if not (retwx['Max-Temp'] or retwx['Min-Temp']):
            parsed_lines[0]['Other-List'], retwx['Max-Temp'], retwx['Min-Temp'] \
                = core.get_temp_min_and_max(parsed_lines[0]['Other-List'])
        parsed_lines = core.find_missing_taf_times(parsed_lines)
        parsed_lines = core.get_taf_flight_rules(parsed_lines)
    else:
        retwx['Min-Temp'] = ['', '']
        retwx['Max-Temp'] = ['', '']
    if retwx['Station'][0] == 'A':
        parsed_lines[len(parsed_lines) - 1]['Other-List'], retwx['Alt-List'], retwx['Temp-List'] \
            = core.get_oceania_temp_and_alt(parsed_lines[len(parsed_lines) - 1]['Other-List'])
    retwx['Forecast'] = parsed_lines
    retwx['Units'] = units
    return retwx
Пример #22
0
 def __init__(self, station: str):
     valid_station(station)
     self.service = service.get_service(station)(
         self.__class__.__name__.lower())
     self.station = station