avwx

AVWX logo

PyPI PyPI - Python Version PyPI - License GitHub - Test Suite Status


Documentation: https://engine.avwx.rest

Source Code: https://github.com/avwx-rest/avwx-engine

PyPI: https://pypi.org/project/avwx-engine/


AVWX is a global aviation weather fetching and parsing engine. It sources reports from a variety of government sources, parses individual elements, and calculates additional information like flight rules and time range interpolation.

AVWX currently supports:

  • Station data and search
  • METAR
  • TAF
  • PIREP
  • AIRMET / SIGMET
  • NOTAM
  • NBM (NBH, NBS, NBE)
  • GFS (MAV, MEX)
>>> import avwx
>>> jfk_metar = avwx.Metar('KJFK')
>>> jfk_metar.update()
True
>>> jfk_metar.data.flight_rules
'VFR'

These docs could use some love, and I am not a writer. You can help by making a pull request on GitHub

Installation

AVWX is available on PyPI and requires Python 3.8 and above. Note: the package name is avwx-engine, but the import is avwx

python -m pip install avwx-engine

Certain features may require additional libraries which most users won't need. For example, finding stations near a coordinate require scipy which is a large package. Attempting to run these methods without the necessary library will prompt you to install them. If you want to install all dependencies at once, run this instead:

python -m pip install avwx-engine[all]

Tutorial

Let's run through a quick example of fetching and parsing a METAR and TAF. There are other report types, but they follow the same basic API.

>>> import avwx
>>> jfk_metar = avwx.Metar('KJFK')
>>> jfk_metar.update()
True
>>> jfk_metar.raw
'KJFK 281651Z 33021G25KT 10SM FEW060 M08/M23 A3054 RMK AO2 SLP339 T10831228'
>>> jfk_metar.data.flight_rules
'VFR'
>>> jfk_metar.summary
'Winds NNW-330 at 21kt gusting to 25kt, Vis 10sm, Temp -08C, Dew -23C, Alt 30.54inHg, Few clouds at 6000ft'
>>> jfk_metar.station.name
'John F Kennedy International Airport'

Here, we create a METAR object and initialize it to pull data for JFK International airport. The update call fetches the current report, parses it into its individual components, and formats the translations. We then view the original report, the calculated flight rules, and a summary string from the translations. We can also see details of the station if available.

>>> hnl_taf = avwx.Taf('PHNL')
>>> hnl_taf.update()
True
>>> hnl_taf.raw
'PHNL 312058Z 3121/0124 07012G19KT P6SM FEW030 SCT050 FM010500 06007KT P6SM FEW025 SCT045 FM012000 07012G19KT P6SM OVC030 SCT050'
>>> len(hnl_taf.data.forecast)
3
>>> for line in hnl_taf.data.forecast:
...   print(f"{line.flight_rules} from {line.start_time.dt.strftime('%d-%H:%M')} to {line.end_time.dt.strftime('%d-%H:%M')}")
...
VFR from 31-21:00 to 01-05:00
VFR from 01-05:00 to 01-20:00
MVFR from 01-20:00 to 01-24:00

Here we start of the same with the Taf object, this time for Honolulu. Because TAFs are forecasts, they contain multiple time periods. Here, we have three: a base and two amendments. Our code shows the different forecasted flight rules for each time period (day-hour). Taf objects have most of the same attributes as Metar objects, so we could also grab the station info if we needed to.

Aviation Reports

  • METAR - Current surface conditions
  • TAF - Localized 24-hour forecast
  • PIREP - Inflight pilot reports
  • AIRMET / SIGMET - Weather advisories
  • NOTAM - Special events or conditions

Model Forecasts

NBM

  • NBH - Current hourly forecast (25 hours)
  • NBS - Short-range forecast (6-72 hours)
  • NBE - Extended-range forecast (24-192 hours)

GFS

  • MOS MAV - Short-range forecast (6-72 hours)
  • MOS MEX - Extended-range forecast (24-192 hours)

Utilities

 1"""
 2.. include:: ../docs/launch.md
 3"""
 4
 5from .current.airsigmet import AirSigmet, AirSigManager
 6from .current.metar import Metar
 7from .current.notam import Notams
 8from .current.pirep import Pireps
 9from .current.taf import Taf
10from .forecast.gfs import Mav, Mex
11from .forecast.nbm import Nbe, Nbh, Nbs, Nbx
12from .station import Station
13
14
15__all__ = (
16    "AirSigManager",
17    "AirSigmet",
18    "Mav",
19    "Metar",
20    "Mex",
21    "Nbe",
22    "Nbh",
23    "Nbs",
24    "Nbx",
25    "Notams",
26    "Pireps",
27    "Station",
28    "Taf",
29)
class AirSigManager:
120class AirSigManager:
121    """
122    Because of the global nature of these report types, we don't initialize a
123    report class with a station ident like the other report types. Instead, we
124    use a class to manage and update the list of all active SIGMET and AIRMET
125    reports.
126
127    ```python
128    >>> from avwx import AirSigManager
129    >>> from avwx.structs import Coord
130    >>> manager = AirSigManager()
131    >>> manager.update()
132    True
133    >>> manager.last_updated
134    datetime.datetime(2022, 3, 27, 5, 54, 21, 516741, tzinfo=datetime.timezone.utc)
135    >>> len(manager.reports)
136    113
137    >>> len(manager.contains(Coord(lat=33.12, lon=-105)))
138    5
139    >>> manager.reports[0].data.bulletin.type
140    Code(repr='WA', value='airmet')
141    >>> manager.reports[0].data.type
142    'AIRMET SIERRA FOR IFR AND MTN OBSCN'
143    ```
144    """
145
146    _services: List[Service]
147    _raw: List[Tuple[str, Optional[str]]]
148    last_updated: Optional[datetime] = None
149    raw: List[str]
150    reports: Optional[List[AirSigmet]] = None
151
152    def __init__(self):  # type: ignore
153        self._services = [NOAA_Bulk("airsigmet"), NOAA_Intl("airsigmet")]
154        self._raw, self.raw = [], []
155
156    async def _update(
157        self, index: int, timeout: int
158    ) -> List[Tuple[str, Optional[str]]]:
159        source = self._services[index].root
160        reports = await self._services[index].async_fetch(timeout=timeout)  # type: ignore
161        raw: List[Tuple[str, Optional[str]]] = [
162            (report, source) for report in reports if report
163        ]
164        return raw
165
166    def update(self, timeout: int = 10, disable_post: bool = False) -> bool:
167        """Updates fetched reports and returns whether they've changed"""
168        return aio.run(self.async_update(timeout, disable_post))
169
170    async def async_update(self, timeout: int = 10, disable_post: bool = False) -> bool:
171        """Updates fetched reports and returns whether they've changed"""
172        coros = [self._update(i, timeout) for i in range(len(self._services))]
173        data = await aio.gather(*coros)
174        raw = list(chain.from_iterable(data))
175        reports = [i[0] for i in raw]
176        if raw == self._raw:
177            return False
178        self._raw, self.raw = raw, reports
179        self.last_updated = datetime.now(tz=timezone.utc)
180        # Parse reports if not disabled
181        if not disable_post:
182            parsed = []
183            for report, source in raw:
184                try:
185                    if obj := AirSigmet.from_report(report):
186                        obj.source = source
187                        parsed.append(obj)
188                except Exception as exc:  # pylint: disable=broad-except
189                    exceptions.exception_intercept(exc, raw={"report": report})
190            self.reports = parsed
191        return True
192
193    def along(self, coords: List[Coord]) -> List[AirSigmet]:
194        """Returns available reports the intersect a flight path"""
195        if LineString is None:
196            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
197        if self.reports is None:
198            return []
199        path = LineString([c.pair for c in coords])
200        return [r for r in self.reports if r.intersects(path)]
201
202    def contains(self, coord: Coord) -> List[AirSigmet]:
203        """Returns available reports that contain a coordinate"""
204        if self.reports is None:
205            return []
206        return [r for r in self.reports if r.contains(coord)]

Because of the global nature of these report types, we don't initialize a report class with a station ident like the other report types. Instead, we use a class to manage and update the list of all active SIGMET and AIRMET reports.

>>> from avwx import AirSigManager
>>> from avwx.structs import Coord
>>> manager = AirSigManager()
>>> manager.update()
True
>>> manager.last_updated
datetime.datetime(2022, 3, 27, 5, 54, 21, 516741, tzinfo=datetime.timezone.utc)
>>> len(manager.reports)
113
>>> len(manager.contains(Coord(lat=33.12, lon=-105)))
5
>>> manager.reports[0].data.bulletin.type
Code(repr='WA', value='airmet')
>>> manager.reports[0].data.type
'AIRMET SIERRA FOR IFR AND MTN OBSCN'
last_updated: Optional[datetime.datetime] = None
raw: List[str]
reports: Optional[List[AirSigmet]] = None
def update(self, timeout: int = 10, disable_post: bool = False) -> bool:
166    def update(self, timeout: int = 10, disable_post: bool = False) -> bool:
167        """Updates fetched reports and returns whether they've changed"""
168        return aio.run(self.async_update(timeout, disable_post))

Updates fetched reports and returns whether they've changed

async def async_update(self, timeout: int = 10, disable_post: bool = False) -> bool:
170    async def async_update(self, timeout: int = 10, disable_post: bool = False) -> bool:
171        """Updates fetched reports and returns whether they've changed"""
172        coros = [self._update(i, timeout) for i in range(len(self._services))]
173        data = await aio.gather(*coros)
174        raw = list(chain.from_iterable(data))
175        reports = [i[0] for i in raw]
176        if raw == self._raw:
177            return False
178        self._raw, self.raw = raw, reports
179        self.last_updated = datetime.now(tz=timezone.utc)
180        # Parse reports if not disabled
181        if not disable_post:
182            parsed = []
183            for report, source in raw:
184                try:
185                    if obj := AirSigmet.from_report(report):
186                        obj.source = source
187                        parsed.append(obj)
188                except Exception as exc:  # pylint: disable=broad-except
189                    exceptions.exception_intercept(exc, raw={"report": report})
190            self.reports = parsed
191        return True

Updates fetched reports and returns whether they've changed

def along( self, coords: List[avwx.structs.Coord]) -> List[AirSigmet]:
193    def along(self, coords: List[Coord]) -> List[AirSigmet]:
194        """Returns available reports the intersect a flight path"""
195        if LineString is None:
196            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
197        if self.reports is None:
198            return []
199        path = LineString([c.pair for c in coords])
200        return [r for r in self.reports if r.intersects(path)]

Returns available reports the intersect a flight path

def contains( self, coord: avwx.structs.Coord) -> List[AirSigmet]:
202    def contains(self, coord: Coord) -> List[AirSigmet]:
203        """Returns available reports that contain a coordinate"""
204        if self.reports is None:
205            return []
206        return [r for r in self.reports if r.contains(coord)]

Returns available reports that contain a coordinate

class AirSigmet(avwx.base.AVWXBase):
 59class AirSigmet(AVWXBase):
 60    """
 61    In addition to the manager, you can use the `avwx.AirSigmet` class like any
 62    other report when you supply the report string via `parse` or
 63    `from_report`.
 64
 65    ```python
 66    >>> from avwx import AirSigmet
 67    >>> report = 'WSPR31 SPJC 270529 SPIM SIGMET 3 VALID 270530/270830 SPJC- SPIM LIMA FIR EMBD TS OBS AT 0510Z NE OF LINE S0406 W07103 - S0358 W07225 - S0235 W07432 - S0114 W07503 TOP FL410 MOV SW NC='
 68    >>> sigmet = AirSigmet.from_report(report)
 69    True
 70    >>> sigmet.last_updated
 71    datetime.datetime(2022, 3, 27, 6, 29, 33, 300935, tzinfo=datetime.timezone.utc)
 72    >>> sigmet.data.observation.coords
 73    [Coord(lat=-4.06, lon=-71.03, repr='S0406 W07103'),
 74    Coord(lat=-3.58, lon=-72.25, repr='S0358 W07225'),
 75    Coord(lat=-2.35, lon=-74.32, repr='S0235 W07432'),
 76    Coord(lat=-1.14, lon=-75.03, repr='S0114 W07503')]
 77    >>> sigmet.data.observation.intensity
 78    Code(repr='NC', value='No change')
 79    >>> sigmet.data.observation.ceiling
 80    Number(repr='FL410', value=410, spoken='flight level four one zero')
 81    ```
 82    """
 83
 84    data: Optional[AirSigmetData] = None
 85
 86    def _post_parse(self) -> None:
 87        if self.raw:
 88            self.data, self.units = parse(self.raw, self.issued)
 89
 90    @staticmethod
 91    def sanitize(report: str) -> str:
 92        """Sanitizes the report string"""
 93        return sanitize(report)
 94
 95    def intersects(self, path: LineString) -> bool:
 96        """Returns True if the report area intersects a flight path"""
 97        if LineString is None:
 98            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
 99        if not self.data:
100            return False
101        for data in (self.data.observation, self.data.forecast):
102            if data:
103                poly = data.poly
104                if poly and path.intersects(poly):
105                    return True
106        return False
107
108    def contains(self, coord: Coord) -> bool:
109        """Returns True if the report area contains a coordinate"""
110        if not self.data:
111            return False
112        for data in (self.data.observation, self.data.forecast):
113            if data:
114                poly = data.poly
115                if poly and coord.point.within(poly):
116                    return True
117        return False

In addition to the manager, you can use the avwx.AirSigmet class like any other report when you supply the report string via parse or from_report.

>>> from avwx import AirSigmet
>>> report = 'WSPR31 SPJC 270529 SPIM SIGMET 3 VALID 270530/270830 SPJC- SPIM LIMA FIR EMBD TS OBS AT 0510Z NE OF LINE S0406 W07103 - S0358 W07225 - S0235 W07432 - S0114 W07503 TOP FL410 MOV SW NC='
>>> sigmet = AirSigmet.from_report(report)
True
>>> sigmet.last_updated
datetime.datetime(2022, 3, 27, 6, 29, 33, 300935, tzinfo=datetime.timezone.utc)
>>> sigmet.data.observation.coords
[Coord(lat=-4.06, lon=-71.03, repr='S0406 W07103'),
Coord(lat=-3.58, lon=-72.25, repr='S0358 W07225'),
Coord(lat=-2.35, lon=-74.32, repr='S0235 W07432'),
Coord(lat=-1.14, lon=-75.03, repr='S0114 W07503')]
>>> sigmet.data.observation.intensity
Code(repr='NC', value='No change')
>>> sigmet.data.observation.ceiling
Number(repr='FL410', value=410, spoken='flight level four one zero')
data: Optional[avwx.structs.AirSigmetData] = None
@staticmethod
def sanitize(report: str) -> str:
90    @staticmethod
91    def sanitize(report: str) -> str:
92        """Sanitizes the report string"""
93        return sanitize(report)

Sanitizes the report string

def intersects(self, path: shapely.geometry.linestring.LineString) -> bool:
 95    def intersects(self, path: LineString) -> bool:
 96        """Returns True if the report area intersects a flight path"""
 97        if LineString is None:
 98            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
 99        if not self.data:
100            return False
101        for data in (self.data.observation, self.data.forecast):
102            if data:
103                poly = data.poly
104                if poly and path.intersects(poly):
105                    return True
106        return False

Returns True if the report area intersects a flight path

def contains(self, coord: avwx.structs.Coord) -> bool:
108    def contains(self, coord: Coord) -> bool:
109        """Returns True if the report area contains a coordinate"""
110        if not self.data:
111            return False
112        for data in (self.data.observation, self.data.forecast):
113            if data:
114                poly = data.poly
115                if poly and coord.point.within(poly):
116                    return True
117        return False

Returns True if the report area contains a coordinate

Inherited Members
avwx.base.AVWXBase
last_updated
issued
source
raw
units
from_report
parse
class Mav(avwx.forecast.base.Forecast):
 49class Mav(Forecast):
 50    '''
 51    The Mav class offers an object-oriented approach to managing MOS MAV data
 52    for a single station.
 53
 54    Below is typical usage for fetching and pulling MAV data for KJFK.
 55
 56    ```python
 57    >>> from avwx import Mav
 58    >>> kjfk = Mav("KJFK")
 59    >>> kjfk.station.name
 60    'John F Kennedy International Airport'
 61    >>> kjfk.update()
 62    True
 63    >>> kjfk.last_updated
 64    datetime.datetime(2020, 4, 20, 1, 7, 7, 393270, tzinfo=datetime.timezone.utc)
 65    >>> print(kjfk.raw)
 66    """
 67    KJFK   GFS MOS GUIDANCE    4/19/2020  1800 UTC
 68    DT /APR  20                  /APR  21                /APR  22
 69    HR   00 03 06 09 12 15 18 21 00 03 06 09 12 15 18 21 00 03 06 12 18
 70    N/X              46          58          44          58       37
 71    TMP  53 52 50 48 48 50 54 56 51 49 47 46 49 53 55 52 47 45 43 41 54
 72    DPT  43 41 37 35 33 30 28 27 28 30 32 34 37 39 37 32 26 23 22 18 14
 73    CLD  OV OV OV OV OV OV OV SC FW CL CL FW BK OV OV OV BK FW CL FW SC
 74    WDR  20 22 26 35 02 03 02 02 34 19 20 18 18 18 18 23 29 30 29 29 28
 75    WSP  20 13 07 08 11 14 14 11 05 03 04 06 11 19 25 21 22 25 20 19 22
 76    P06         0    12     9     1     0     1    29    68     8  2  0
 77    P12              12           9           2          69       15
 78    Q06         0     0     0     0     0     0     0     2     0  0  0
 79    Q12               0           0           0           2        0
 80    T06      0/ 4  1/ 0  1/ 0  0/ 0  0/ 0  0/ 0  5/ 3 13/13  0/ 0  0/ 8
 81    T12                  1/ 2        0/ 0        9/ 6       14/13  1/ 8
 82    POZ   0  1  1  0  0  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0
 83    POS   0  0  0  0  0  2  0  6  6  9  9  0 16  8  0  4  4 47 60 67 42
 84    TYP   R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  S  S  R
 85    SNW                                       0                    0
 86    CIG   7  7  7  7  6  6  6  8  8  8  8  8  8  6  6  6  7  8  8  8  8
 87    VIS   7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  6  7  7  7  7  7
 88    OBV   N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N
 89    """
 90    >>> len(kjfk.data.forecast)
 91    21
 92    >>> kjfk.data.forecast[0].ceiling
 93    Code(repr='7', value='6600 - 12,000 feet')
 94    ```
 95
 96    The `parse` and `from_report` methods can parse a report string if you want
 97    to override the normal fetching process.
 98    '''
 99
100    report_type = "mav"
101    _service_class = NOAA_GFS  # type: ignore
102
103    async def _post_update(self) -> None:
104        if self.raw is None:
105            return
106        self.data = parse_mav(self.raw)
107        self.units = Units(**static.UNITS)
108
109    def _post_parse(self) -> None:
110        if self.raw is None:
111            return
112        self.data = parse_mav(self.raw)
113        self.units = Units(**static.UNITS)

The Mav class offers an object-oriented approach to managing MOS MAV data for a single station.

Below is typical usage for fetching and pulling MAV data for KJFK.

>>> from avwx import Mav
>>> kjfk = Mav("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2020, 4, 20, 1, 7, 7, 393270, tzinfo=datetime.timezone.utc)
>>> print(kjfk.raw)
"""
KJFK   GFS MOS GUIDANCE    4/19/2020  1800 UTC
DT /APR  20                  /APR  21                /APR  22
HR   00 03 06 09 12 15 18 21 00 03 06 09 12 15 18 21 00 03 06 12 18
N/X              46          58          44          58       37
TMP  53 52 50 48 48 50 54 56 51 49 47 46 49 53 55 52 47 45 43 41 54
DPT  43 41 37 35 33 30 28 27 28 30 32 34 37 39 37 32 26 23 22 18 14
CLD  OV OV OV OV OV OV OV SC FW CL CL FW BK OV OV OV BK FW CL FW SC
WDR  20 22 26 35 02 03 02 02 34 19 20 18 18 18 18 23 29 30 29 29 28
WSP  20 13 07 08 11 14 14 11 05 03 04 06 11 19 25 21 22 25 20 19 22
P06         0    12     9     1     0     1    29    68     8  2  0
P12              12           9           2          69       15
Q06         0     0     0     0     0     0     0     2     0  0  0
Q12               0           0           0           2        0
T06      0/ 4  1/ 0  1/ 0  0/ 0  0/ 0  0/ 0  5/ 3 13/13  0/ 0  0/ 8
T12                  1/ 2        0/ 0        9/ 6       14/13  1/ 8
POZ   0  1  1  0  0  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0
POS   0  0  0  0  0  2  0  6  6  9  9  0 16  8  0  4  4 47 60 67 42
TYP   R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  R  S  S  R
SNW                                       0                    0
CIG   7  7  7  7  6  6  6  8  8  8  8  8  8  6  6  6  7  8  8  8  8
VIS   7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  6  7  7  7  7  7
OBV   N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N  N
"""
>>> len(kjfk.data.forecast)
21
>>> kjfk.data.forecast[0].ceiling
Code(repr='7', value='6600 - 12,000 feet')

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

report_type = 'mav'
Inherited Members
avwx.forecast.base.Forecast
Forecast
service
avwx.base.ManagedReport
code
station
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
data
units
parse
sanitize
class Metar(avwx.current.base.Report):
 38class Metar(Report):
 39    """
 40    The Metar class offers an object-oriented approach to managing METAR data
 41    for a single station.
 42
 43    Below is typical usage for fetching and pulling METAR data for KJFK.
 44
 45    ```python
 46    >>> from avwx import Metar
 47    >>> kjfk = Metar("KJFK")
 48    >>> kjfk.station.name
 49    'John F Kennedy International Airport'
 50    >>> kjfk.update()
 51    True
 52    >>> kjfk.last_updated
 53    datetime.datetime(2018, 3, 4, 23, 36, 6, 62376)
 54    >>> kjfk.raw
 55    'KJFK 042251Z 32023G32KT 10SM BKN060 04/M08 A3008 RMK AO2 PK WND 32032/2251 SLP184 T00441078'
 56    >>> kjfk.data.flight_rules
 57    'VFR'
 58    >>> kjfk.translations.remarks
 59    {'AO2': 'Automated with precipitation sensor', 'SLP184': 'Sea level pressure: 1018.4 hPa', 'T00441078': 'Temperature 4.4°C and dewpoint -7.8°C'}
 60    ```
 61
 62    The `parse` and `from_report` methods can parse a report string if you want
 63    to override the normal fetching process. Here's an example of a really bad
 64    day.
 65
 66    ```python
 67    >>> from avwx import Metar
 68    >>> report = 'KSFO 031254Z 36024G55KT 320V040 1/8SM R06/0200D +TS VCFC OVC050 BKN040TCU 14/10 A2978 RMK AIRPORT CLOSED'
 69    >>> ksfo = Metar.from_report(report)
 70    True
 71    >>> ksfo.station.city
 72    'San Francisco'
 73    >>> ksfo.last_updated
 74    datetime.datetime(2018, 3, 4, 23, 54, 4, 353757, tzinfo=datetime.timezone.utc)
 75    >>> ksfo.data.flight_rules
 76    'LIFR'
 77    >>> ksfo.translations.clouds
 78    'Broken layer at 4000ft (Towering Cumulus), Overcast layer at 5000ft - Reported AGL'
 79    >>> ksfo.summary
 80    'Winds N-360 (variable 320 to 040) at 24kt gusting to 55kt, Vis 0.125sm, Temp 14C, Dew 10C, Alt 29.78inHg, Heavy Thunderstorm, Vicinity Funnel Cloud, Broken layer at 4000ft (Towering Cumulus), Overcast layer at 5000ft'
 81    ```
 82    """
 83
 84    data: Optional[MetarData] = None
 85    translations: Optional[MetarTrans] = None
 86
 87    async def _pull_from_default(self) -> None:
 88        """Checks for a more recent report from NOAA. Only sync"""
 89        service = NOAA(self.__class__.__name__.lower())
 90        if self.code is None:
 91            return
 92        report = await service.async_fetch(self.code)
 93        if report is not None:
 94            data, units, sans = parse(self.code, report, self.issued)
 95            if not data or data.time is None or data.time.dt is None:
 96                return
 97            if (
 98                not self.data
 99                or self.data.time is None
100                or self.data.time.dt is None
101                or data.time.dt > self.data.time.dt
102            ):
103                self.data, self.units, self.sanitization = data, units, sans
104                self.source = service.root
105
106    @property
107    def _should_check_default(self) -> bool:
108        """Returns True if pulled from regional source and potentially out of date"""
109        if isinstance(self.service, NOAA) or self.source is None:
110            return False
111
112        if self.data is None or self.data.time is None or self.data.time.dt is None:
113            return True
114        time_since = datetime.now(tz=timezone.utc) - self.data.time.dt
115        return time_since > timedelta(minutes=90)
116
117    def _calculate_altitudes(self) -> None:
118        """Adds the pressure and density altitudes to data if all fields are available"""
119        if self.data is None or self.station is None or self.units is None:
120            return
121        # Select decimal temperature if available
122        temp = self.data.temperature
123        if self.data.remarks_info is not None:
124            temp = self.data.remarks_info.temperature_decimal or temp
125        alt = self.data.altimeter
126        if temp is None or temp.value is None or alt is None or alt.value is None:
127            return
128        elev = self.station.elevation_ft
129        if elev is None:
130            return
131        self.data.pressure_altitude = core.pressure_altitude(
132            alt.value, elev, self.units.altimeter
133        )
134        self.data.density_altitude = core.density_altitude(
135            alt.value, temp.value, elev, self.units
136        )
137
138    async def _post_update(self) -> None:
139        if self.code is None or self.raw is None:
140            return
141        self.data, self.units, self.sanitization = parse(
142            self.code, self.raw, self.issued
143        )
144        if self._should_check_default:
145            await self._pull_from_default()
146        if self.data is None or self.units is None:
147            return
148        self._calculate_altitudes()
149        self.translations = translate_metar(self.data, self.units)
150
151    def _post_parse(self) -> None:
152        if self.code is None or self.raw is None:
153            return
154        self.data, self.units, self.sanitization = parse(
155            self.code, self.raw, self.issued
156        )
157        if self.data is None or self.units is None:
158            return
159        self._calculate_altitudes()
160        self.translations = translate_metar(self.data, self.units)
161
162    @staticmethod
163    def sanitize(report: str) -> str:
164        """Sanitizes a METAR string"""
165        return sanitize(report)[0]
166
167    @property
168    def summary(self) -> Optional[str]:
169        """Condensed report summary created from translations"""
170        if not self.translations:
171            self.update()
172        return None if self.translations is None else summary.metar(self.translations)
173
174    @property
175    def speech(self) -> Optional[str]:
176        """Report summary designed to be read by a text-to-speech program"""
177        if not self.data:
178            self.update()
179        if self.data is None or self.units is None:
180            return None
181        return speech.metar(self.data, self.units)

The Metar class offers an object-oriented approach to managing METAR data for a single station.

Below is typical usage for fetching and pulling METAR data for KJFK.

>>> from avwx import Metar
>>> kjfk = Metar("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2018, 3, 4, 23, 36, 6, 62376)
>>> kjfk.raw
'KJFK 042251Z 32023G32KT 10SM BKN060 04/M08 A3008 RMK AO2 PK WND 32032/2251 SLP184 T00441078'
>>> kjfk.data.flight_rules
'VFR'
>>> kjfk.translations.remarks
{'AO2': 'Automated with precipitation sensor', 'SLP184': 'Sea level pressure: 1018.4 hPa', 'T00441078': 'Temperature 4.4°C and dewpoint -7.8°C'}

The parse and from_report methods can parse a report string if you want to override the normal fetching process. Here's an example of a really bad day.

>>> from avwx import Metar
>>> report = 'KSFO 031254Z 36024G55KT 320V040 1/8SM R06/0200D +TS VCFC OVC050 BKN040TCU 14/10 A2978 RMK AIRPORT CLOSED'
>>> ksfo = Metar.from_report(report)
True
>>> ksfo.station.city
'San Francisco'
>>> ksfo.last_updated
datetime.datetime(2018, 3, 4, 23, 54, 4, 353757, tzinfo=datetime.timezone.utc)
>>> ksfo.data.flight_rules
'LIFR'
>>> ksfo.translations.clouds
'Broken layer at 4000ft (Towering Cumulus), Overcast layer at 5000ft - Reported AGL'
>>> ksfo.summary
'Winds N-360 (variable 320 to 040) at 24kt gusting to 55kt, Vis 0.125sm, Temp 14C, Dew 10C, Alt 29.78inHg, Heavy Thunderstorm, Vicinity Funnel Cloud, Broken layer at 4000ft (Towering Cumulus), Overcast layer at 5000ft'
data: Optional[avwx.structs.MetarData] = None
translations: Optional[avwx.structs.MetarTrans] = None
@staticmethod
def sanitize(report: str) -> str:
162    @staticmethod
163    def sanitize(report: str) -> str:
164        """Sanitizes a METAR string"""
165        return sanitize(report)[0]

Sanitizes a METAR string

summary: Optional[str]
167    @property
168    def summary(self) -> Optional[str]:
169        """Condensed report summary created from translations"""
170        if not self.translations:
171            self.update()
172        return None if self.translations is None else summary.metar(self.translations)

Condensed report summary created from translations

speech: Optional[str]
174    @property
175    def speech(self) -> Optional[str]:
176        """Report summary designed to be read by a text-to-speech program"""
177        if not self.data:
178            self.update()
179        if self.data is None or self.units is None:
180            return None
181        return speech.metar(self.data, self.units)

Report summary designed to be read by a text-to-speech program

Inherited Members
avwx.current.base.Report
Report
sanitization
avwx.base.ManagedReport
code
station
service
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
units
parse
class Mex(avwx.forecast.base.Forecast):
116class Mex(Forecast):
117    '''
118    The Mex class offers an object-oriented approach to managing MOS MEX data
119    for a single station.
120
121    Below is typical usage for fetching and pulling MAV data for KJFK.
122
123    ```python
124    >>> from avwx import Mex
125    >>> kjfk = Mex("KJFK")
126    >>> kjfk.station.name
127    'John F Kennedy International Airport'
128    >>> kjfk.update()
129    True
130    >>> kjfk.last_updated
131    datetime.datetime(2020, 4, 20, 12, 7, 7, 393270, tzinfo=datetime.timezone.utc)
132    >>> print(kjfk.raw)
133    """
134    KJFK   GFSX MOS GUIDANCE   4/20/2020  0000 UTC
135    FHR  24| 36  48| 60  72| 84  96|108 120|132 144|156 168|180 192
136    MON  20| TUE 21| WED 22| THU 23| FRI 24| SAT 25| SUN 26| MON 27 CLIMO
137    X/N  57| 45  59| 37  56| 40  52| 49  58| 46  59| 48  59| 44  58 45 63
138    TMP  50| 49  48| 41  49| 45  48| 52  51| 50  53| 51  52| 48  51
139    DPT  31| 39  26| 17  17| 24  40| 46  43| 40  44| 43  40| 35  31
140    CLD  OV| OV  OV| CL  CL| OV  OV| OV  OV| PC  OV| OV  OV| OV  OV
141    WND  13| 14  26| 26  21| 16  13| 18  15| 16  12| 15  19| 19  11
142    P12   9|  1  73|  7   0|  9  43| 73  63| 27  51| 64  37| 35  32 24 23
143    P24    |     73|      7|     43|     77|     61|     73|     44    36
144    Q12   0|  0   2|  0   0|  0   1|  5   3|  0   2|  5    |
145    Q24    |      1|      0|      0|      5|      2|       |
146    T12   1|  0  12|  1   0|  4   4|  8  11|  3   3| 14   7|  5   9
147    T24    |  1    | 14    |  4    | 12    | 11    | 14    | 11
148    PZP   0|  0   1|  0   2|  4   1|  0   0|  0   0|  0   0|  0   0
149    PSN   0|  0   0| 37  25| 15   4|  0   0|  0   0|  2   0|  3   5
150    PRS   0|  2   1| 32  28| 19   4|  0   1|  1   1|  1   1|  8   9
151    TYP   R|  R   R| RS  RS|  R   R|  R   R|  R   R|  R   R|  R   R
152    SNW    |      0|      0|      0|      0|      0|       |
153    """
154    >>> len(kjfk.data.forecast)
155    15
156    >>> kjfk.data.forecast[2].precip_chance_24
157    Number(repr='73', value=73, spoken='seven three')
158    ```
159
160    The `parse` and `from_report` methods can parse a report string if you want
161    to override the normal fetching process.
162    '''
163
164    report_type = "mex"
165    _service_class = NOAA_GFS  # type: ignore
166
167    async def _post_update(self) -> None:
168        if self.raw is None:
169            return
170        self.data = parse_mex(self.raw)
171        self.units = Units(**static.UNITS)
172
173    def _post_parse(self) -> None:
174        if self.raw is None:
175            return
176        self.data = parse_mex(self.raw)
177        self.units = Units(**static.UNITS)

The Mex class offers an object-oriented approach to managing MOS MEX data for a single station.

Below is typical usage for fetching and pulling MAV data for KJFK.

>>> from avwx import Mex
>>> kjfk = Mex("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2020, 4, 20, 12, 7, 7, 393270, tzinfo=datetime.timezone.utc)
>>> print(kjfk.raw)
"""
KJFK   GFSX MOS GUIDANCE   4/20/2020  0000 UTC
FHR  24| 36  48| 60  72| 84  96|108 120|132 144|156 168|180 192
MON  20| TUE 21| WED 22| THU 23| FRI 24| SAT 25| SUN 26| MON 27 CLIMO
X/N  57| 45  59| 37  56| 40  52| 49  58| 46  59| 48  59| 44  58 45 63
TMP  50| 49  48| 41  49| 45  48| 52  51| 50  53| 51  52| 48  51
DPT  31| 39  26| 17  17| 24  40| 46  43| 40  44| 43  40| 35  31
CLD  OV| OV  OV| CL  CL| OV  OV| OV  OV| PC  OV| OV  OV| OV  OV
WND  13| 14  26| 26  21| 16  13| 18  15| 16  12| 15  19| 19  11
P12   9|  1  73|  7   0|  9  43| 73  63| 27  51| 64  37| 35  32 24 23
P24    |     73|      7|     43|     77|     61|     73|     44    36
Q12   0|  0   2|  0   0|  0   1|  5   3|  0   2|  5    |
Q24    |      1|      0|      0|      5|      2|       |
T12   1|  0  12|  1   0|  4   4|  8  11|  3   3| 14   7|  5   9
T24    |  1    | 14    |  4    | 12    | 11    | 14    | 11
PZP   0|  0   1|  0   2|  4   1|  0   0|  0   0|  0   0|  0   0
PSN   0|  0   0| 37  25| 15   4|  0   0|  0   0|  2   0|  3   5
PRS   0|  2   1| 32  28| 19   4|  0   1|  1   1|  1   1|  8   9
TYP   R|  R   R| RS  RS|  R   R|  R   R|  R   R|  R   R|  R   R
SNW    |      0|      0|      0|      0|      0|       |
"""
>>> len(kjfk.data.forecast)
15
>>> kjfk.data.forecast[2].precip_chance_24
Number(repr='73', value=73, spoken='seven three')

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

report_type = 'mex'
Inherited Members
avwx.forecast.base.Forecast
Forecast
service
avwx.base.ManagedReport
code
station
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
data
units
parse
sanitize
class Nbe(avwx.forecast.nbm._Nbm):
351class Nbe(_Nbm):
352    '''
353    Class to handle NBM NBE report data
354
355    Below is typical usage for fetching and pulling NBE data for KJFK.
356
357    ```python
358    >>> from avwx import Nbe
359    >>> kjfk = Nbe("KJFK")
360    >>> kjfk.station.name
361    'John F Kennedy International Airport'
362    >>> kjfk.update()
363    True
364    >>> kjfk.last_updated
365    datetime.datetime(2020, 7, 28, 1, 23, 4, 909939, tzinfo=datetime.timezone.utc)
366    >>> print(kjfk.raw)
367    """
368    KJFK    NBM V3.2 NBE GUIDANCE    7/28/2020  0000 UTC
369           WED 29| THU 30| FRI 31| SAT 01| SUN 02| MON 03| TUE 04|WED CLIMO
370    UTC    00  12| 00  12| 00  12| 00  12| 00  12| 00  12| 00  12| 00
371    FHR    24  36| 48  60| 72  84| 96 108|120 132|144 156|168 180|192
372    X/N    93  76| 91  76| 90  74| 86  72| 87  73| 85  74| 86  72| 84 68 83
373    TMP    84  80| 83  80| 81  78| 78  76| 78  78| 78  78| 78  76| 76
374    DPT    72  69| 68  69| 71  68| 67  66| 68  69| 70  71| 70  68| 69
375    SKY    61  21| 23  47| 80  73| 47  31| 30  54| 68  65| 66  59| 32
376    WDR    25  35| 20  26| 20   2| 16   1| 16   7| 16  24| 22  34| 18
377    WSP     5   2|  6   3|  5   4|  3   5|  7   4|  6   4|  5   4|  4
378    GST    11   4| 13   6| 13  10|  9  10| 13   7| 13   9| 16   9| 12
379    P12    48  23|  8   1| 23  28| 28  16| 18  17| 30  41| 46  31| 32 19 18
380    Q12    10   0|  0   0|  0   0|  0   0|  0   0|  0  64| 77  81| 83
381    Q24          |  0    |  0    |  0    |  0    |  0    |141    |164
382    DUR     2   1|  0   0|  0   0|  0   0|  0   0|  2  12| 12  12| 12
383    T12    46  32|  6   8| 21  22| 17   5|  6   5| 25  23| 19  18| 18
384    PZR     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
385    PSN     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
386    PPL     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
387    PRA   100 100|100 100|100 100|100 100|100 100|100 100|100 100|100
388    S12     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
389    SLV   138 114|111 119|119 121|113 101|108 117|134 132|124 123|121
390    I12     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
391    S24          |  0    |  0    |  0    |  0    |  0    |  0    |  0
392    SOL   100 320|190 230|270 250|360 290|370  30|190 260|250 230|450
393    """
394    >>> len(kjfk.data.forecast)
395    25
396    >>> kjfk.data.forecast[0].wind_direction
397    Number(repr='25', value=250, spoken='two five zero')
398    >>> print(kjfk.data.forecast[1].precip_duration.value, kjfk.units.duration)
399    1 hour
400    ```
401
402    The `parse` and `from_report` methods can parse a report string if you want
403    to override the normal fetching process.
404    '''
405
406    report_type = "nbe"
407    _parser = staticmethod(parse_nbe)

Class to handle NBM NBE report data

Below is typical usage for fetching and pulling NBE data for KJFK.

>>> from avwx import Nbe
>>> kjfk = Nbe("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2020, 7, 28, 1, 23, 4, 909939, tzinfo=datetime.timezone.utc)
>>> print(kjfk.raw)
"""
KJFK    NBM V3.2 NBE GUIDANCE    7/28/2020  0000 UTC
       WED 29| THU 30| FRI 31| SAT 01| SUN 02| MON 03| TUE 04|WED CLIMO
UTC    00  12| 00  12| 00  12| 00  12| 00  12| 00  12| 00  12| 00
FHR    24  36| 48  60| 72  84| 96 108|120 132|144 156|168 180|192
X/N    93  76| 91  76| 90  74| 86  72| 87  73| 85  74| 86  72| 84 68 83
TMP    84  80| 83  80| 81  78| 78  76| 78  78| 78  78| 78  76| 76
DPT    72  69| 68  69| 71  68| 67  66| 68  69| 70  71| 70  68| 69
SKY    61  21| 23  47| 80  73| 47  31| 30  54| 68  65| 66  59| 32
WDR    25  35| 20  26| 20   2| 16   1| 16   7| 16  24| 22  34| 18
WSP     5   2|  6   3|  5   4|  3   5|  7   4|  6   4|  5   4|  4
GST    11   4| 13   6| 13  10|  9  10| 13   7| 13   9| 16   9| 12
P12    48  23|  8   1| 23  28| 28  16| 18  17| 30  41| 46  31| 32 19 18
Q12    10   0|  0   0|  0   0|  0   0|  0   0|  0  64| 77  81| 83
Q24          |  0    |  0    |  0    |  0    |  0    |141    |164
DUR     2   1|  0   0|  0   0|  0   0|  0   0|  2  12| 12  12| 12
T12    46  32|  6   8| 21  22| 17   5|  6   5| 25  23| 19  18| 18
PZR     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
PSN     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
PPL     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
PRA   100 100|100 100|100 100|100 100|100 100|100 100|100 100|100
S12     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
SLV   138 114|111 119|119 121|113 101|108 117|134 132|124 123|121
I12     0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0   0|  0
S24          |  0    |  0    |  0    |  0    |  0    |  0    |  0
SOL   100 320|190 230|270 250|360 290|370  30|190 260|250 230|450
"""
>>> len(kjfk.data.forecast)
25
>>> kjfk.data.forecast[0].wind_direction
Number(repr='25', value=250, spoken='two five zero')
>>> print(kjfk.data.forecast[1].precip_duration.value, kjfk.units.duration)
1 hour

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

report_type = 'nbe'
Inherited Members
avwx.forecast.base.Forecast
Forecast
service
avwx.forecast.nbm._Nbm
units
avwx.base.ManagedReport
code
station
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
data
parse
sanitize
class Nbh(avwx.forecast.nbm._Nbm):
222class Nbh(_Nbm):
223    '''
224    Class to handle NBM NBH report data
225
226    Below is typical usage for fetching and pulling NBH data for KJFK.
227
228    ```python
229    >>> from avwx import Nbh
230    >>> kjfk = Nbh("KJFK")
231    >>> kjfk.station.name
232    'John F Kennedy International Airport'
233    >>> kjfk.update()
234    True
235    >>> kjfk.last_updated
236    datetime.datetime(2020, 7, 26, 20, 37, 42, 352220, tzinfo=datetime.timezone.utc)
237    >>> print(kjfk.raw)
238    """
239    KJFK   NBM V3.2 NBH GUIDANCE    7/26/2020  1900 UTC
240    UTC  20 21 22 23 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
241    TMP  90 89 88 87 85 84 82 81 81 80 79 78 77 76 76 78 81 84 87 89 90 91 92 92 93
242    DPT  69 68 66 66 65 65 65 66 66 66 66 67 67 66 67 67 67 67 67 68 67 67 67 67 67
243    SKY   9 14 41 58 61 71 66 55 39 37 39 43 40 38 29 21 19 26 24 27 22 14 22 26 26
244    WDR  22 22 23 24 25 25 25 25 25 25 25 26 26 26 26 26 26 26 25 24 24 23 23 22 23
245    WSP  10  9  9  8  7  6  5  5  5  6  5  5  5  5  4  4  5  5  7  8  8  9 10 10 10
246    GST  17 16 16 15 14 12 11 12 12 12 12 11 11  9  9  9  9 10 11 13 14 15 16 17 17
247    P01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
248    P06                                 0                 0                 0
249    Q01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
250    DUR                                                   0
251    T01   1  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
252    PZR   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
253    PSN   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
254    PPL   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
255    PRA 100100100100100100100100100100100100100100100100100100100100100100100100100
256    S01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
257    SLV  99101102103105105105106105104106104104104104103102100 99 98 98 99100101102
258    I01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
259    CIG 888888888360360350340888888888888888888888888888888888888888888888888888888
260    VIS 110120120140140130120120120120110110110110110120130140150150150150150150150
261    LCB  80 60999999999999999999999999999999999999999999999999999999999999999999999
262    MHT  26 26 20 19  8  7  6  4  4  5  5  5  6  4  5  6  6 11 20 27 37 39 43 39 31
263    TWD  24 26 26 27 26 26 24 23 24 26 26 26 27 27 28 27 27 27 27 26 26 26 25 25 24
264    TWS  15 14 14 13 11 11 11 10 11 11 10 11 10  8  8  7  8  9 12 13 14 15 17 17 16
265    HID                                 6                 5                 6
266    SOL 710500430250110  0 30  0  0  0  0  0  0  0 30160330500650760830870870850720
267    """
268    >>> len(kjfk.data.forecast)
269    25
270    >>> kjfk.data.forecast[0].snow_level
271    Number(repr='99', value=9900, spoken='nine nine hundred')
272    >>> print(kjfk.data.forecast[0].solar_radiation.value, kjfk.units.solar_radiation)
273    710 W/m2
274    ```
275
276    The `parse` and `from_report` methods can parse a report string if you want
277    to override the normal fetching process.
278    '''
279
280    report_type = "nbh"
281    _parser = staticmethod(parse_nbh)

Class to handle NBM NBH report data

Below is typical usage for fetching and pulling NBH data for KJFK.

>>> from avwx import Nbh
>>> kjfk = Nbh("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2020, 7, 26, 20, 37, 42, 352220, tzinfo=datetime.timezone.utc)
>>> print(kjfk.raw)
"""
KJFK   NBM V3.2 NBH GUIDANCE    7/26/2020  1900 UTC
UTC  20 21 22 23 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
TMP  90 89 88 87 85 84 82 81 81 80 79 78 77 76 76 78 81 84 87 89 90 91 92 92 93
DPT  69 68 66 66 65 65 65 66 66 66 66 67 67 66 67 67 67 67 67 68 67 67 67 67 67
SKY   9 14 41 58 61 71 66 55 39 37 39 43 40 38 29 21 19 26 24 27 22 14 22 26 26
WDR  22 22 23 24 25 25 25 25 25 25 25 26 26 26 26 26 26 26 25 24 24 23 23 22 23
WSP  10  9  9  8  7  6  5  5  5  6  5  5  5  5  4  4  5  5  7  8  8  9 10 10 10
GST  17 16 16 15 14 12 11 12 12 12 12 11 11  9  9  9  9 10 11 13 14 15 16 17 17
P01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
P06                                 0                 0                 0
Q01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
DUR                                                   0
T01   1  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PZR   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PSN   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PPL   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PRA 100100100100100100100100100100100100100100100100100100100100100100100100100
S01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
SLV  99101102103105105105106105104106104104104104103102100 99 98 98 99100101102
I01   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
CIG 888888888360360350340888888888888888888888888888888888888888888888888888888
VIS 110120120140140130120120120120110110110110110120130140150150150150150150150
LCB  80 60999999999999999999999999999999999999999999999999999999999999999999999
MHT  26 26 20 19  8  7  6  4  4  5  5  5  6  4  5  6  6 11 20 27 37 39 43 39 31
TWD  24 26 26 27 26 26 24 23 24 26 26 26 27 27 28 27 27 27 27 26 26 26 25 25 24
TWS  15 14 14 13 11 11 11 10 11 11 10 11 10  8  8  7  8  9 12 13 14 15 17 17 16
HID                                 6                 5                 6
SOL 710500430250110  0 30  0  0  0  0  0  0  0 30160330500650760830870870850720
"""
>>> len(kjfk.data.forecast)
25
>>> kjfk.data.forecast[0].snow_level
Number(repr='99', value=9900, spoken='nine nine hundred')
>>> print(kjfk.data.forecast[0].solar_radiation.value, kjfk.units.solar_radiation)
710 W/m2

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

report_type = 'nbh'
Inherited Members
avwx.forecast.base.Forecast
Forecast
service
avwx.forecast.nbm._Nbm
units
avwx.base.ManagedReport
code
station
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
data
parse
sanitize
class Nbs(avwx.forecast.nbm._Nbm):
284class Nbs(_Nbm):
285    '''
286    Class to handle NBM NBS report data
287
288    Below is typical usage for fetching and pulling Nbs data for KJFK.
289
290    ```python
291    >>> from avwx import Nbs
292    >>> kjfk = Nbs("KJFK")
293    >>> kjfk.station.name
294    'John F Kennedy International Airport'
295    >>> kjfk.update()
296    True
297    >>> kjfk.last_updated
298    datetime.datetime(2020, 7, 28, 1, 3, 46, 447635, tzinfo=datetime.timezone.utc)
299    >>> print(kjfk.raw)
300    """
301    KJFK    NBM V3.2 NBS GUIDANCE    7/27/2020  2300 UTC
302    DT /JULY 28               /JULY 29                /JULY 30
303    UTC  03 06 09 12 15 18 21 00 03 06 09 12 15 18 21 00 03 06 09 12 15 18 21
304    FHR  04 07 10 13 16 19 22 25 28 31 34 37 40 43 46 49 52 55 58 61 64 67 70
305    N/X           79          93          76          91          76
306    TMP  85 82 80 83 89 91 89 84 81 79 77 80 85 89 87 83 81 79 77 80 86 88 86
307    DPT  70 70 71 72 72 72 73 72 72 71 69 69 68 67 68 69 68 67 68 69 68 69 70
308    SKY   4 10  2  4 12 23 38 61 53 62 51 26 19  9 21 24 25 34 32 45 57 70 79
309    WDR  23 24 23 24 24 22 23 27 28 28 34 35 21 20 19 22 23 25 26 26 23 20 20
310    WSP   8  8  5  6  8  9  7  5  3  2  1  2  3  6  9  7  4  4  3  3  4  7  8
311    GST  16 15 11 11 13 15 15 11  9  5  4  4  6 12 15 13 11 11  8  6  7 13 15
312    P06      0     1    15    48    17    11     8     8     1     0     5
313    P12            1          48          17           8           1
314    Q06      0     0     0    11     0     0     0     0     0     0     0
315    Q12            0          11           0           0           0
316    DUR            0           2           0           0           0
317    T03   2  3  1  1  2 10 27 30 21 13  8  5  1  0  2  3  4  3  2  3  1  3  7
318    T12            4          48          33           6           8
319    PZR   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
320    PSN   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
321    PPL   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
322    PRA 100100100100100100100100100100100100100100100100100100100100100100100
323    S06      0     0     0     0     0     0     0     0     0     0     0
324    SLV 108112113133135139141139137134124113102 99107115114118118119118118118
325    I06      0     0     0     0     0     0     0     0     0     0     0
326    CIG 888888888888888888888170888150888888888888888888888888888888888888888
327    VIS 120120110120130130130110110110110130110110110120120120110110110110110
328    LCB  60 70999 90 90999 50 60 60 60 60 60 22999999200230 70 80 60 60150 60
329    MHT   7  6  5 13 30 46 19 15  7  7 10 16 40 35 13  4  4  4  9 18 31 27 13
330    TWD  23 24 23 27 25 24 27 27 28 21 34  6 26 23 20 19 26 23 27 29 24 20 18
331    TWS  17 16  9 11 13 16 12  9  5  5  4  3  4  8 11 10  8 10 10  6  6  9 13
332    HID      4     4     4     4     3     4     4     5     5     3     4
333    SOL   0  0  0320700760360100  0  0  0320720830620190  0  0  0230480570540
334    """
335    >>> len(kjfk.data.forecast)
336    23
337    >>> kjfk.data.forecast[0].ceiling
338    Number(repr='888', value=None, spoken='unlimited')
339    >>> print(kjfk.data.forecast[7].precip_amount_12.value, kjfk.units.accumulation)
340    0.11 in
341    ```
342
343    The `parse` and `from_report` methods can parse a report string if you want
344    to override the normal fetching process.
345    '''
346
347    report_type = "nbs"
348    _parser = staticmethod(parse_nbs)

Class to handle NBM NBS report data

Below is typical usage for fetching and pulling Nbs data for KJFK.

>>> from avwx import Nbs
>>> kjfk = Nbs("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2020, 7, 28, 1, 3, 46, 447635, tzinfo=datetime.timezone.utc)
>>> print(kjfk.raw)
"""
KJFK    NBM V3.2 NBS GUIDANCE    7/27/2020  2300 UTC
DT /JULY 28               /JULY 29                /JULY 30
UTC  03 06 09 12 15 18 21 00 03 06 09 12 15 18 21 00 03 06 09 12 15 18 21
FHR  04 07 10 13 16 19 22 25 28 31 34 37 40 43 46 49 52 55 58 61 64 67 70
N/X           79          93          76          91          76
TMP  85 82 80 83 89 91 89 84 81 79 77 80 85 89 87 83 81 79 77 80 86 88 86
DPT  70 70 71 72 72 72 73 72 72 71 69 69 68 67 68 69 68 67 68 69 68 69 70
SKY   4 10  2  4 12 23 38 61 53 62 51 26 19  9 21 24 25 34 32 45 57 70 79
WDR  23 24 23 24 24 22 23 27 28 28 34 35 21 20 19 22 23 25 26 26 23 20 20
WSP   8  8  5  6  8  9  7  5  3  2  1  2  3  6  9  7  4  4  3  3  4  7  8
GST  16 15 11 11 13 15 15 11  9  5  4  4  6 12 15 13 11 11  8  6  7 13 15
P06      0     1    15    48    17    11     8     8     1     0     5
P12            1          48          17           8           1
Q06      0     0     0    11     0     0     0     0     0     0     0
Q12            0          11           0           0           0
DUR            0           2           0           0           0
T03   2  3  1  1  2 10 27 30 21 13  8  5  1  0  2  3  4  3  2  3  1  3  7
T12            4          48          33           6           8
PZR   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PSN   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PPL   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
PRA 100100100100100100100100100100100100100100100100100100100100100100100
S06      0     0     0     0     0     0     0     0     0     0     0
SLV 108112113133135139141139137134124113102 99107115114118118119118118118
I06      0     0     0     0     0     0     0     0     0     0     0
CIG 888888888888888888888170888150888888888888888888888888888888888888888
VIS 120120110120130130130110110110110130110110110120120120110110110110110
LCB  60 70999 90 90999 50 60 60 60 60 60 22999999200230 70 80 60 60150 60
MHT   7  6  5 13 30 46 19 15  7  7 10 16 40 35 13  4  4  4  9 18 31 27 13
TWD  23 24 23 27 25 24 27 27 28 21 34  6 26 23 20 19 26 23 27 29 24 20 18
TWS  17 16  9 11 13 16 12  9  5  5  4  3  4  8 11 10  8 10 10  6  6  9 13
HID      4     4     4     4     3     4     4     5     5     3     4
SOL   0  0  0320700760360100  0  0  0320720830620190  0  0  0230480570540
"""
>>> len(kjfk.data.forecast)
23
>>> kjfk.data.forecast[0].ceiling
Number(repr='888', value=None, spoken='unlimited')
>>> print(kjfk.data.forecast[7].precip_amount_12.value, kjfk.units.accumulation)
0.11 in

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

report_type = 'nbs'
Inherited Members
avwx.forecast.base.Forecast
Forecast
service
avwx.forecast.nbm._Nbm
units
avwx.base.ManagedReport
code
station
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
data
parse
sanitize
class Nbx(avwx.forecast.nbm._Nbm):
410class Nbx(_Nbm):
411    '''
412    Class to handle NBM NBX report data
413
414    Below is typical usage for fetching and pulling NBX data for KJFK.
415
416    ```python
417    >>> from avwx import Nbx
418    >>> kjfk = Nbx("KJFK")
419    >>> kjfk.station.name
420    'John F Kennedy International Airport'
421    >>> kjfk.update()
422    True
423    >>> kjfk.last_updated
424    datetime.datetime(2023, 10, 17, 4, 0, 0, 909939, tzinfo=datetime.timezone.utc)
425    >>> print(kjfk.raw)
426    """
427    086092  NBM V4.1 NBX GUIDANCE   10/17/2023  0300 UTC
428    WED 25 |THU 26 |FRI 27 |SAT 28
429    UTC 12 |00  12 |00  12 |00
430    FHR 201|213 225|237 249|261
431    TXN  76| 81  75| 81  75| 81
432    XND   1|  1   1|  2   1|  0
433    TMP  77| 77  77| 78  76| 78
434    TSD   1|  2   1|  1   2|  1
435    DPT  67| 67  69| 68  70| 69
436    DSD   1|  2   1|  1   2|  1
437    SKY  34| 46  27| 50  26| 37
438    SSD  12| 22  13| 22  12|  7
439    WDR   7|  7   7|  6   5|  5
440    WSP  15| 16  15| 17  15| 17
441    WSD   3|  4   3|  3   4|  3
442    GST  22| 24  22| 25  22| 25
443    GSD   2|  2   2|  1   1|  1
444    P12   8|  8   9|  7   7|  7
445    Q12   0|  0   0|  3   0|  0
446    Q24   0|      0|      3|
447    DUR   0|  0   0|  0   0|  0
448    PZR   0|  0   0|  0   0|  0
449    PSN   0|  0   0|  0   0|  0
450    PPL   0|  0   0|  0   0|  0
451    PRA   7| 16  14| 10   6| 11
452    S12   0|  0   0|  0   0|  0
453    I12   0|  0   0|  0   0|  0
454    SOL   1| 35   1| 15   1| 38
455    """
456    >>> len(kjfk.data.forecast)
457    25
458    >>> kjfk.data.forecast[0].wind_speed
459    Number(repr='15', value=150, spoken='one five zero')
460    >>> print(kjfk.data.forecast[1].solar_radiation.value, kjfk.units.solar_radiation)
461    35 W/m2
462    ```
463
464    The `parse` and `from_report` methods can parse a report string if you want
465    to override the normal fetching process.
466    '''
467
468    report_type = "nbx"
469    _parser = staticmethod(parse_nbx)

Class to handle NBM NBX report data

Below is typical usage for fetching and pulling NBX data for KJFK.

>>> from avwx import Nbx
>>> kjfk = Nbx("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2023, 10, 17, 4, 0, 0, 909939, tzinfo=datetime.timezone.utc)
>>> print(kjfk.raw)
"""
086092  NBM V4.1 NBX GUIDANCE   10/17/2023  0300 UTC
WED 25 |THU 26 |FRI 27 |SAT 28
UTC 12 |00  12 |00  12 |00
FHR 201|213 225|237 249|261
TXN  76| 81  75| 81  75| 81
XND   1|  1   1|  2   1|  0
TMP  77| 77  77| 78  76| 78
TSD   1|  2   1|  1   2|  1
DPT  67| 67  69| 68  70| 69
DSD   1|  2   1|  1   2|  1
SKY  34| 46  27| 50  26| 37
SSD  12| 22  13| 22  12|  7
WDR   7|  7   7|  6   5|  5
WSP  15| 16  15| 17  15| 17
WSD   3|  4   3|  3   4|  3
GST  22| 24  22| 25  22| 25
GSD   2|  2   2|  1   1|  1
P12   8|  8   9|  7   7|  7
Q12   0|  0   0|  3   0|  0
Q24   0|      0|      3|
DUR   0|  0   0|  0   0|  0
PZR   0|  0   0|  0   0|  0
PSN   0|  0   0|  0   0|  0
PPL   0|  0   0|  0   0|  0
PRA   7| 16  14| 10   6| 11
S12   0|  0   0|  0   0|  0
I12   0|  0   0|  0   0|  0
SOL   1| 35   1| 15   1| 38
"""
>>> len(kjfk.data.forecast)
25
>>> kjfk.data.forecast[0].wind_speed
Number(repr='15', value=150, spoken='one five zero')
>>> print(kjfk.data.forecast[1].solar_radiation.value, kjfk.units.solar_radiation)
35 W/m2

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

report_type = 'nbx'
Inherited Members
avwx.forecast.base.Forecast
Forecast
service
avwx.forecast.nbm._Nbm
units
avwx.base.ManagedReport
code
station
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
data
parse
sanitize
class Notams(avwx.current.base.Reports):
 67class Notams(Reports):
 68    '''
 69    The Notams class provides two ways of requesting all applicable NOTAMs in
 70    an area: airport code and coordinate. The service will fetch all reports
 71    within 10 nautical miles of the desired center point. You can change the
 72    distance by updating the `Notams.radius` member before calling `update()`.
 73
 74    ```python
 75    >>> from pprint import pprint
 76    >>> from avwx import Notams
 77    >>> from avwx.structs import Coord
 78    >>>
 79    >>> kjfk = Notams("KJFK")
 80    >>> kjfk.update()
 81    True
 82    >>> kjfk.last_updated
 83    datetime.datetime(2022, 5, 26, 0, 43, 22, 44753, tzinfo=datetime.timezone.utc)
 84    >>> print(kjfk.data[0].raw)
 85    01/113 NOTAMN
 86    Q) ZNY/QMXLC/IV/NBO/A/000/999/4038N07346W005
 87    A) KJFK
 88    B) 2101081328
 89    C) 2209301100
 90
 91    E) TWY TB BTN TERMINAL 8 RAMP AND TWY A CLSD
 92    >>> pprint(kjfk.data[0].qualifiers)
 93    Qualifiers(repr='ZNY/QMXLC/IV/NBO/A/000/999/4038N07346W005',
 94            fir='ZNY',
 95            subject=Code(repr='MX', value='Taxiway'),
 96            condition=Code(repr='LC', value='Closed'),
 97            traffic=Code(repr='IV', value='IFR and VFR'),
 98            purpose=[Code(repr='N', value='Immediate'),
 99                        Code(repr='B', value='Briefing'),
100                        Code(repr='O', value='Flight Operations')],
101            scope=[Code(repr='A', value='Aerodrome')],
102            lower=Number(repr='000', value=0, spoken='zero'),
103            upper=Number(repr='999', value=999, spoken='nine nine nine'),
104            coord=Coord(lat=40.38, lon=-73.46, repr='4038N07346W'),
105            radius=Number(repr='005', value=5, spoken='five'))
106    >>>
107    >>> coord = Notams(coord=Coord(lat=52, lon=-0.23))
108    >>> coord.update()
109    True
110    >>> coord.data[0].station
111    'EGSS'
112    >>> print(coord.data[0].body)
113    LONDON STANSTED ATC SURVEILLANCE MINIMUM ALTITUDE CHART - IN
114    FREQUENCY BOX RENAME ESSEX RADAR TO STANSTED RADAR.
115    UK AIP AD 2.EGSS-5-1 REFERS
116    ```
117
118    The `parse` and `from_report` methods can parse a report string if you want
119    to override the normal fetching process.
120
121    ```python
122    >>> from avwx import Notams
123    >>> report = """
124    05/295 NOTAMR
125    Q) ZNY/QMNHW/IV/NBO/A/000/999/4038N07346W005
126    A) KJFK
127    B) 2205201527
128    C) 2205271100
129
130    E) APRON TERMINAL 4 RAMP CONST WIP S SIDE TAXILANE G LGTD AND BARRICADED
131    """
132    >>> kjfk = Notams.from_report(report)
133    >>> kjfk.data[0].type
134    Code(repr='NOTAMR', value='Replace')
135    >>> kjfk.data[0].start_time
136    Timestamp(repr='2205201527', dt=datetime.datetime(2022, 5, 20, 15, 27, tzinfo=datetime.timezone.utc))
137    ```
138    '''
139
140    data: Optional[List[NotamData]] = None  # type: ignore
141    radius: int = 10
142
143    def __init__(self, code: Optional[str] = None, coord: Optional[Coord] = None):
144        super().__init__(code, coord)
145        self.service = FAA_NOTAM("notam")
146
147    async def _post_update(self) -> None:
148        self._post_parse()
149
150    def _post_parse(self) -> None:
151        self.data, units = [], None
152        if self.raw is None:
153            return
154        for report in self.raw:
155            if "||" in report:
156                issue_text, report = report.split("||")
157                issued_value = datetime.strptime(issue_text, r"%m/%d/%Y %H%M")
158                issued = Timestamp(issue_text, issued_value)
159            else:
160                issued = None
161            try:
162                data, units = parse(report, issued=issued)
163                self.data.append(data)
164            except Exception as exc:  # pylint: disable=broad-except
165                exceptions.exception_intercept(exc, raw=report)  # type: ignore
166        if units:
167            self.units = units
168
169    @staticmethod
170    def sanitize(report: str) -> str:
171        """Sanitizes a NOTAM string"""
172        return sanitize(report)
173
174    async def async_update(self, timeout: int = 10, disable_post: bool = False) -> bool:
175        """Async updates report data by fetching and parsing the report"""
176        reports = await self.service.async_fetch(  # type: ignore
177            icao=self.code, coord=self.coord, radius=self.radius, timeout=timeout
178        )
179        self.source = self.service.root
180        return await self._update(reports, None, disable_post)

The Notams class provides two ways of requesting all applicable NOTAMs in an area: airport code and coordinate. The service will fetch all reports within 10 nautical miles of the desired center point. You can change the distance by updating the Notams.radius member before calling update().

>>> from pprint import pprint
>>> from avwx import Notams
>>> from avwx.structs import Coord
>>>
>>> kjfk = Notams("KJFK")
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2022, 5, 26, 0, 43, 22, 44753, tzinfo=datetime.timezone.utc)
>>> print(kjfk.data[0].raw)
01/113 NOTAMN
Q) ZNY/QMXLC/IV/NBO/A/000/999/4038N07346W005
A) KJFK
B) 2101081328
C) 2209301100

E) TWY TB BTN TERMINAL 8 RAMP AND TWY A CLSD
>>> pprint(kjfk.data[0].qualifiers)
Qualifiers(repr='ZNY/QMXLC/IV/NBO/A/000/999/4038N07346W005',
        fir='ZNY',
        subject=Code(repr='MX', value='Taxiway'),
        condition=Code(repr='LC', value='Closed'),
        traffic=Code(repr='IV', value='IFR and VFR'),
        purpose=[Code(repr='N', value='Immediate'),
                    Code(repr='B', value='Briefing'),
                    Code(repr='O', value='Flight Operations')],
        scope=[Code(repr='A', value='Aerodrome')],
        lower=Number(repr='000', value=0, spoken='zero'),
        upper=Number(repr='999', value=999, spoken='nine nine nine'),
        coord=Coord(lat=40.38, lon=-73.46, repr='4038N07346W'),
        radius=Number(repr='005', value=5, spoken='five'))
>>>
>>> coord = Notams(coord=Coord(lat=52, lon=-0.23))
>>> coord.update()
True
>>> coord.data[0].station
'EGSS'
>>> print(coord.data[0].body)
LONDON STANSTED ATC SURVEILLANCE MINIMUM ALTITUDE CHART - IN
FREQUENCY BOX RENAME ESSEX RADAR TO STANSTED RADAR.
UK AIP AD 2.EGSS-5-1 REFERS

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

>>> from avwx import Notams
>>> report = """
05/295 NOTAMR
Q) ZNY/QMNHW/IV/NBO/A/000/999/4038N07346W005
A) KJFK
B) 2205201527
C) 2205271100

E) APRON TERMINAL 4 RAMP CONST WIP S SIDE TAXILANE G LGTD AND BARRICADED
"""
>>> kjfk = Notams.from_report(report)
>>> kjfk.data[0].type
Code(repr='NOTAMR', value='Replace')
>>> kjfk.data[0].start_time
Timestamp(repr='2205201527', dt=datetime.datetime(2022, 5, 20, 15, 27, tzinfo=datetime.timezone.utc))
Notams( code: Optional[str] = None, coord: Optional[avwx.structs.Coord] = None)
143    def __init__(self, code: Optional[str] = None, coord: Optional[Coord] = None):
144        super().__init__(code, coord)
145        self.service = FAA_NOTAM("notam")
data: Optional[List[avwx.structs.NotamData]] = None
radius: int = 10
service
@staticmethod
def sanitize(report: str) -> str:
169    @staticmethod
170    def sanitize(report: str) -> str:
171        """Sanitizes a NOTAM string"""
172        return sanitize(report)

Sanitizes a NOTAM string

async def async_update(self, timeout: int = 10, disable_post: bool = False) -> bool:
174    async def async_update(self, timeout: int = 10, disable_post: bool = False) -> bool:
175        """Async updates report data by fetching and parsing the report"""
176        reports = await self.service.async_fetch(  # type: ignore
177            icao=self.code, coord=self.coord, radius=self.radius, timeout=timeout
178        )
179        self.source = self.service.root
180        return await self._update(reports, None, disable_post)

Async updates report data by fetching and parsing the report

Inherited Members
avwx.current.base.Reports
coord
raw
units
sanitization
parse
async_parse
update
avwx.base.ManagedReport
code
station
from_report
avwx.base.AVWXBase
last_updated
issued
source
class Pireps(avwx.current.base.Reports):
 40class Pireps(Reports):
 41    """
 42    The Pireps class offers an object-oriented approach to managing multiple
 43    PIREP reports for a single station.
 44
 45    Below is typical usage for fetching and pulling PIREP data for KJFK.
 46
 47    ```python
 48    >>> from avwx import Pireps
 49    >>> kmco = Pireps("KMCO")
 50    >>> kmco.station.name
 51    'Orlando International Airport'
 52    >>> kmco.update()
 53    True
 54    >>> kmco.last_updated
 55    datetime.datetime(2019, 5, 24, 13, 31, 46, 561732, tzinfo=datetime.timezone.utc)
 56    >>> kmco.raw[0]
 57    'FLL UA /OV KFLL275015/TM 1241/FL020/TP B737/SK TOP020/RM DURD RY10L'
 58    >>> kmco.data[0].location
 59    Location(repr='KFLL275015', station='KFLL', direction=Number(repr='275', value=275, spoken='two seven five'), distance=Number(repr='015', value=15, spoken='one five'))
 60    ```
 61
 62    The `parse` and `from_report` methods can parse a report string if you want
 63    to override the normal fetching process.
 64    """
 65
 66    data: Optional[List[Optional[PirepData]]] = None  # type: ignore
 67    sanitization: Optional[List[Optional[Sanitization]]] = None  # type: ignore
 68
 69    def __init__(self, code: Optional[str] = None, coord: Optional[Coord] = None):
 70        super().__init__(code, coord)
 71        self.service = NOAA_ScrapeList("pirep")
 72
 73    @staticmethod
 74    def _report_filter(reports: List[str]) -> List[str]:
 75        """Removes AIREPs before updating raw_reports"""
 76        return [r for r in reports if not r.startswith("ARP")]
 77
 78    async def _post_update(self) -> None:
 79        self.data, self.sanitization = [], []
 80        if self.raw is None:
 81            return
 82        for report in self.raw:
 83            try:
 84                data, sans = parse(report, issued=self.issued)
 85                self.data.append(data)
 86                self.sanitization.append(sans)
 87            except Exception as exc:  # pylint: disable=broad-except
 88                exceptions.exception_intercept(exc, raw=report)  # type: ignore
 89
 90    def _post_parse(self) -> None:
 91        self.data, self.sanitization = [], []
 92        if self.raw is None:
 93            return
 94        for report in self.raw:
 95            data, sans = parse(report, issued=self.issued)
 96            self.data.append(data)
 97            self.sanitization.append(sans)
 98
 99    @staticmethod
100    def sanitize(report: str) -> str:
101        """Sanitizes a PIREP string"""
102        return sanitize(report)[0]

The Pireps class offers an object-oriented approach to managing multiple PIREP reports for a single station.

Below is typical usage for fetching and pulling PIREP data for KJFK.

>>> from avwx import Pireps
>>> kmco = Pireps("KMCO")
>>> kmco.station.name
'Orlando International Airport'
>>> kmco.update()
True
>>> kmco.last_updated
datetime.datetime(2019, 5, 24, 13, 31, 46, 561732, tzinfo=datetime.timezone.utc)
>>> kmco.raw[0]
'FLL UA /OV KFLL275015/TM 1241/FL020/TP B737/SK TOP020/RM DURD RY10L'
>>> kmco.data[0].location
Location(repr='KFLL275015', station='KFLL', direction=Number(repr='275', value=275, spoken='two seven five'), distance=Number(repr='015', value=15, spoken='one five'))

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

Pireps( code: Optional[str] = None, coord: Optional[avwx.structs.Coord] = None)
69    def __init__(self, code: Optional[str] = None, coord: Optional[Coord] = None):
70        super().__init__(code, coord)
71        self.service = NOAA_ScrapeList("pirep")
data: Optional[List[Optional[avwx.structs.PirepData]]] = None
sanitization: Optional[List[Optional[avwx.structs.Sanitization]]] = None
service
@staticmethod
def sanitize(report: str) -> str:
 99    @staticmethod
100    def sanitize(report: str) -> str:
101        """Sanitizes a PIREP string"""
102        return sanitize(report)[0]

Sanitizes a PIREP string

Inherited Members
avwx.current.base.Reports
coord
raw
units
parse
async_parse
update
async_update
avwx.base.ManagedReport
code
station
from_report
avwx.base.AVWXBase
last_updated
issued
source
@dataclass
class Station:
 53@dataclass
 54class Station:
 55    """
 56    The Station dataclass stores basic info about the desired station and
 57    available Runways.
 58
 59    The easiest way to get a station is to supply the ICAO, IATA, or GPS code.
 60    The example below uses `from_code` which checks against all three types,
 61    but you can also use `from_icao`, `from_iata`, or `from_gps` if you know
 62    what type of code you are using. This can be important if you may be using
 63    a code used by more than one station depending on the context. ICAO and
 64    IATA codes are guarenteed unique, but not all airports have them. That
 65    said, all stations available in AVWX have either an ICAO or GPS code.
 66
 67    ```python
 68    >>> from avwx import Station
 69    >>> klex = Station.from_code("KLEX")
 70    >>> f"{klex.name} in {klex.city}, {klex.state}"
 71    'Blue Grass Airport in Lexington, KY'
 72    >>> coord = round(klex.latitude, 3), round(klex.longitude, 3)
 73    >>> f"Located at {coord} at {klex.elevation_ft} feet ({klex.elevation_m} meters)"
 74    'Located at (38.036, -84.606) at 979 feet (298 meters)'
 75    >>> rw = max(klex.runways, key=lambda r: r.length_ft)
 76    >>> f"Its longest runway is {rw.ident1}/{rw.ident2} at {rw.length_ft} feet"
 77    'Its longest runway is 04/22 at 7003 feet'
 78    ```
 79
 80    This is also the same information you'd get from calling Report.station.
 81
 82    ```python
 83    >>> from avwx import Metar
 84    >>> klex = Metar('KLEX')
 85    >>> klex.station.name
 86    'Blue Grass Airport'
 87    ```
 88    """
 89
 90    # pylint: disable=too-many-instance-attributes
 91
 92    city: Optional[str]
 93    country: str
 94    elevation_ft: Optional[int]
 95    elevation_m: Optional[int]
 96    gps: Optional[str]
 97    iata: Optional[str]
 98    icao: Optional[str]
 99    latitude: float
100    local: Optional[str]
101    longitude: float
102    name: str
103    note: Optional[str]
104    reporting: bool
105    runways: List[Runway]
106    state: Optional[str]
107    type: str
108    website: Optional[str]
109    wiki: Optional[str]
110
111    @classmethod
112    def _from_code(cls: Type[T], ident: str) -> T:
113        try:
114            info: Dict[str, Any] = copy(STATIONS[ident])
115            if info["runways"]:
116                info["runways"] = [Runway(**r) for r in info["runways"]]
117            return cls(**info)
118        except (KeyError, AttributeError) as not_found:
119            raise BadStation(
120                f"Could not find station with ident {ident}"
121            ) from not_found
122
123    @classmethod
124    def from_code(cls: Type[T], ident: str) -> T:
125        """Load a Station from ICAO, GPS, or IATA code in that order"""
126        if ident and isinstance(ident, str):
127            if len(ident) == 4:
128                with suppress(BadStation):
129                    return cls.from_icao(ident)
130                with suppress(BadStation):
131                    return cls.from_gps(ident)
132            if len(ident) == 3:
133                with suppress(BadStation):
134                    return cls.from_iata(ident)
135            with suppress(BadStation):
136                return cls.from_local(ident)
137        raise BadStation(f"Could not find station with ident {ident}")
138
139    @classmethod
140    def from_icao(cls: Type[T], ident: str) -> T:
141        """Load a Station from an ICAO station ident"""
142        try:
143            return cls._from_code(_ICAO.value[ident.upper()])
144        except (KeyError, AttributeError) as not_found:
145            raise BadStation(
146                f"Could not find station with ICAO ident {ident}"
147            ) from not_found
148
149    @classmethod
150    def from_iata(cls: Type[T], ident: str) -> T:
151        """Load a Station from an IATA code"""
152        try:
153            return cls._from_code(_IATA.value[ident.upper()])
154        except (KeyError, AttributeError) as not_found:
155            raise BadStation(
156                f"Could not find station with IATA ident {ident}"
157            ) from not_found
158
159    @classmethod
160    def from_gps(cls: Type[T], ident: str) -> T:
161        """Load a Station from a GPS code"""
162        try:
163            return cls._from_code(_GPS.value[ident.upper()])
164        except (KeyError, AttributeError) as not_found:
165            raise BadStation(
166                f"Could not find station with GPS ident {ident}"
167            ) from not_found
168
169    @classmethod
170    def from_local(cls: Type[T], ident: str) -> T:
171        """Load a Station from a local code"""
172        try:
173            return cls._from_code(_LOCAL.value[ident.upper()])
174        except (KeyError, AttributeError) as not_found:
175            raise BadStation(
176                f"Could not find station with local ident {ident}"
177            ) from not_found
178
179    @classmethod
180    def nearest(
181        cls: Type[T],
182        lat: Optional[float] = None,
183        lon: Optional[float] = None,
184        is_airport: bool = False,
185        sends_reports: bool = True,
186        max_coord_distance: float = 10,
187    ) -> Optional[Tuple[T, dict]]:
188        """Load the Station nearest to your location or a lat,lon coordinate pair
189
190        Returns the Station and distances from source
191
192        NOTE: Becomes less accurate toward poles and doesn't cross +/-180
193        """
194        if not (lat and lon):
195            lat, lon = _get_ip_location().pair
196        ret = nearest(lat, lon, 1, is_airport, sends_reports, max_coord_distance)
197        if not isinstance(ret, dict):
198            return None
199        station = ret.pop("station")
200        return station, ret
201
202    @property
203    def lookup_code(self) -> str:
204        """Returns the ICAO or GPS code for report fetch"""
205        if self.icao:
206            return self.icao
207        if self.gps:
208            return self.gps
209        raise BadStation("Station does not have a valid lookup code")
210
211    @property
212    def storage_code(self) -> str:
213        """Returns the first unique-ish code from what's available"""
214        if self.icao:
215            return self.icao
216        if self.iata:
217            return self.iata
218        if self.gps:
219            return self.gps
220        if self.local:
221            return self.local
222        raise BadStation("Station does not have any useable codes")
223
224    @property
225    def sends_reports(self) -> bool:
226        """Returns whether or not a Station likely sends weather reports"""
227        return self.reporting is True
228
229    @property
230    def coord(self) -> Coord:
231        """Returns the station location as a Coord"""
232        return Coord(lat=self.latitude, lon=self.longitude, repr=self.icao)
233
234    def distance(self, lat: float, lon: float) -> Distance:
235        """Returns a geopy Distance using the great circle method"""
236        return great_circle((lat, lon), (self.latitude, self.longitude))
237
238    def nearby(
239        self,
240        is_airport: bool = False,
241        sends_reports: bool = True,
242        max_coord_distance: float = 10,
243    ) -> List[Tuple[T, dict]]:
244        """Returns Stations nearest to current station and their distances
245
246        NOTE: Becomes less accurate toward poles and doesn't cross +/-180
247        """
248        stations = nearest(
249            self.latitude,
250            self.longitude,
251            11,
252            is_airport,
253            sends_reports,
254            max_coord_distance,
255        )
256        if isinstance(stations, dict):
257            return []
258        return [(s.pop("station"), s) for s in stations[1:]]

The Station dataclass stores basic info about the desired station and available Runways.

The easiest way to get a station is to supply the ICAO, IATA, or GPS code. The example below uses from_code which checks against all three types, but you can also use from_icao, from_iata, or from_gps if you know what type of code you are using. This can be important if you may be using a code used by more than one station depending on the context. ICAO and IATA codes are guarenteed unique, but not all airports have them. That said, all stations available in AVWX have either an ICAO or GPS code.

>>> from avwx import Station
>>> klex = Station.from_code("KLEX")
>>> f"{klex.name} in {klex.city}, {klex.state}"
'Blue Grass Airport in Lexington, KY'
>>> coord = round(klex.latitude, 3), round(klex.longitude, 3)
>>> f"Located at {coord} at {klex.elevation_ft} feet ({klex.elevation_m} meters)"
'Located at (38.036, -84.606) at 979 feet (298 meters)'
>>> rw = max(klex.runways, key=lambda r: r.length_ft)
>>> f"Its longest runway is {rw.ident1}/{rw.ident2} at {rw.length_ft} feet"
'Its longest runway is 04/22 at 7003 feet'

This is also the same information you'd get from calling Report.station.

>>> from avwx import Metar
>>> klex = Metar('KLEX')
>>> klex.station.name
'Blue Grass Airport'
Station( city: Optional[str], country: str, elevation_ft: Optional[int], elevation_m: Optional[int], gps: Optional[str], iata: Optional[str], icao: Optional[str], latitude: float, local: Optional[str], longitude: float, name: str, note: Optional[str], reporting: bool, runways: List[avwx.station.station.Runway], state: Optional[str], type: str, website: Optional[str], wiki: Optional[str])
city: Optional[str]
country: str
elevation_ft: Optional[int]
elevation_m: Optional[int]
gps: Optional[str]
iata: Optional[str]
icao: Optional[str]
latitude: float
local: Optional[str]
longitude: float
name: str
note: Optional[str]
reporting: bool
runways: List[avwx.station.station.Runway]
state: Optional[str]
type: str
website: Optional[str]
wiki: Optional[str]
@classmethod
def from_code(cls: Type[~T], ident: str) -> ~T:
123    @classmethod
124    def from_code(cls: Type[T], ident: str) -> T:
125        """Load a Station from ICAO, GPS, or IATA code in that order"""
126        if ident and isinstance(ident, str):
127            if len(ident) == 4:
128                with suppress(BadStation):
129                    return cls.from_icao(ident)
130                with suppress(BadStation):
131                    return cls.from_gps(ident)
132            if len(ident) == 3:
133                with suppress(BadStation):
134                    return cls.from_iata(ident)
135            with suppress(BadStation):
136                return cls.from_local(ident)
137        raise BadStation(f"Could not find station with ident {ident}")

Load a Station from ICAO, GPS, or IATA code in that order

@classmethod
def from_icao(cls: Type[~T], ident: str) -> ~T:
139    @classmethod
140    def from_icao(cls: Type[T], ident: str) -> T:
141        """Load a Station from an ICAO station ident"""
142        try:
143            return cls._from_code(_ICAO.value[ident.upper()])
144        except (KeyError, AttributeError) as not_found:
145            raise BadStation(
146                f"Could not find station with ICAO ident {ident}"
147            ) from not_found

Load a Station from an ICAO station ident

@classmethod
def from_iata(cls: Type[~T], ident: str) -> ~T:
149    @classmethod
150    def from_iata(cls: Type[T], ident: str) -> T:
151        """Load a Station from an IATA code"""
152        try:
153            return cls._from_code(_IATA.value[ident.upper()])
154        except (KeyError, AttributeError) as not_found:
155            raise BadStation(
156                f"Could not find station with IATA ident {ident}"
157            ) from not_found

Load a Station from an IATA code

@classmethod
def from_gps(cls: Type[~T], ident: str) -> ~T:
159    @classmethod
160    def from_gps(cls: Type[T], ident: str) -> T:
161        """Load a Station from a GPS code"""
162        try:
163            return cls._from_code(_GPS.value[ident.upper()])
164        except (KeyError, AttributeError) as not_found:
165            raise BadStation(
166                f"Could not find station with GPS ident {ident}"
167            ) from not_found

Load a Station from a GPS code

@classmethod
def from_local(cls: Type[~T], ident: str) -> ~T:
169    @classmethod
170    def from_local(cls: Type[T], ident: str) -> T:
171        """Load a Station from a local code"""
172        try:
173            return cls._from_code(_LOCAL.value[ident.upper()])
174        except (KeyError, AttributeError) as not_found:
175            raise BadStation(
176                f"Could not find station with local ident {ident}"
177            ) from not_found

Load a Station from a local code

@classmethod
def nearest( cls: Type[~T], lat: Optional[float] = None, lon: Optional[float] = None, is_airport: bool = False, sends_reports: bool = True, max_coord_distance: float = 10) -> Optional[Tuple[~T, dict]]:
179    @classmethod
180    def nearest(
181        cls: Type[T],
182        lat: Optional[float] = None,
183        lon: Optional[float] = None,
184        is_airport: bool = False,
185        sends_reports: bool = True,
186        max_coord_distance: float = 10,
187    ) -> Optional[Tuple[T, dict]]:
188        """Load the Station nearest to your location or a lat,lon coordinate pair
189
190        Returns the Station and distances from source
191
192        NOTE: Becomes less accurate toward poles and doesn't cross +/-180
193        """
194        if not (lat and lon):
195            lat, lon = _get_ip_location().pair
196        ret = nearest(lat, lon, 1, is_airport, sends_reports, max_coord_distance)
197        if not isinstance(ret, dict):
198            return None
199        station = ret.pop("station")
200        return station, ret

Load the Station nearest to your location or a lat,lon coordinate pair

Returns the Station and distances from source

NOTE: Becomes less accurate toward poles and doesn't cross +/-180

lookup_code: str
202    @property
203    def lookup_code(self) -> str:
204        """Returns the ICAO or GPS code for report fetch"""
205        if self.icao:
206            return self.icao
207        if self.gps:
208            return self.gps
209        raise BadStation("Station does not have a valid lookup code")

Returns the ICAO or GPS code for report fetch

storage_code: str
211    @property
212    def storage_code(self) -> str:
213        """Returns the first unique-ish code from what's available"""
214        if self.icao:
215            return self.icao
216        if self.iata:
217            return self.iata
218        if self.gps:
219            return self.gps
220        if self.local:
221            return self.local
222        raise BadStation("Station does not have any useable codes")

Returns the first unique-ish code from what's available

sends_reports: bool
224    @property
225    def sends_reports(self) -> bool:
226        """Returns whether or not a Station likely sends weather reports"""
227        return self.reporting is True

Returns whether or not a Station likely sends weather reports

coord: avwx.structs.Coord
229    @property
230    def coord(self) -> Coord:
231        """Returns the station location as a Coord"""
232        return Coord(lat=self.latitude, lon=self.longitude, repr=self.icao)

Returns the station location as a Coord

def distance(self, lat: float, lon: float) -> geopy.distance.Distance:
234    def distance(self, lat: float, lon: float) -> Distance:
235        """Returns a geopy Distance using the great circle method"""
236        return great_circle((lat, lon), (self.latitude, self.longitude))

Returns a geopy Distance using the great circle method

def nearby( self, is_airport: bool = False, sends_reports: bool = True, max_coord_distance: float = 10) -> List[Tuple[~T, dict]]:
238    def nearby(
239        self,
240        is_airport: bool = False,
241        sends_reports: bool = True,
242        max_coord_distance: float = 10,
243    ) -> List[Tuple[T, dict]]:
244        """Returns Stations nearest to current station and their distances
245
246        NOTE: Becomes less accurate toward poles and doesn't cross +/-180
247        """
248        stations = nearest(
249            self.latitude,
250            self.longitude,
251            11,
252            is_airport,
253            sends_reports,
254            max_coord_distance,
255        )
256        if isinstance(stations, dict):
257            return []
258        return [(s.pop("station"), s) for s in stations[1:]]

Returns Stations nearest to current station and their distances

NOTE: Becomes less accurate toward poles and doesn't cross +/-180

class Taf(avwx.current.base.Report):
 35class Taf(Report):
 36    """
 37    The Taf class offers an object-oriented approach to managing TAF data for a
 38    single station.
 39
 40    ```python
 41    >>> from avwx import Taf
 42    >>> kjfk = Taf("KJFK")
 43    >>> kjfk.station.name
 44    'John F Kennedy International Airport'
 45    >>> kjfk.update()
 46    True
 47    >>> kjfk.last_updated
 48    datetime.datetime(2018, 3, 4, 23, 43, 26, 209644, tzinfo=datetime.timezone.utc)
 49    >>> kjfk.raw
 50    'KJFK 042030Z 0421/0524 33016G27KT P6SM BKN045 FM051600 36016G22KT P6SM BKN040 FM052100 35013KT P6SM SCT035'
 51    >>> len(kjfk.data.forecast)
 52    3
 53    >>> kjfk.data.forecast[0].flight_rules
 54    'VFR'
 55    >>> kjfk.translations.forecast[0].wind
 56    'NNW-330 at 16kt gusting to 27kt'
 57    >>> kjfk.speech
 58    'Starting on March 4th - From 21 to 16 zulu, Winds three three zero at 16kt gusting to 27kt. Visibility greater than six miles. Broken layer at 4500ft. From 16 to 21 zulu, Winds three six zero at 16kt gusting to 22kt. Visibility greater than six miles. Broken layer at 4000ft. From 21 to midnight zulu, Winds three five zero at 13kt. Visibility greater than six miles. Scattered clouds at 3500ft'
 59    ```
 60
 61    The `parse` and `from_report` methods can parse a report string if you want
 62    to override the normal fetching process.
 63
 64    ```python
 65    >>> from avwx import Taf
 66    >>> report = "TAF ZYHB 082300Z 0823/0911 VRB03KT 9999 SCT018 BKN120 TX14/0907Z TN04/0921Z FM090100 09015KT 9999 -SHRA WS020/13045KT SCT018 BKN120 BECMG 0904/0906 34008KT PROB30 TEMPO 0906/0911 7000 -RA SCT020 650104 530804 RMK FCST BASED ON AUTO OBS. NXT FCST BY 090600Z"
 67    >>> zyhb = Taf.from_report(report)
 68    True
 69    >>> zyhb.station.city
 70    'Hulan'
 71    >>> zyhb.data.remarks
 72    'RMK FCST BASED ON AUTO OBS. NXT FCST BY 090600Z'
 73    >>> zyhb.summary[-1]
 74    'Vis 7km, Light Rain, Scattered clouds at 2000ft, Frequent moderate turbulence in clear air from 8000ft to 12000ft, Moderate icing in clouds from 1000ft to 5000ft'
 75    ```
 76    """
 77
 78    data: Optional[TafData] = None
 79    translations: Optional[TafTrans] = None  # type: ignore
 80
 81    async def _post_update(self) -> None:
 82        if self.code is None or self.raw is None:
 83            return
 84        self.data, self.units, self.sanitization = parse(
 85            self.code, self.raw, self.issued
 86        )
 87        if self.data is None or self.units is None:
 88            return
 89        self.translations = translate_taf(self.data, self.units)
 90
 91    def _post_parse(self) -> None:
 92        if self.code is None or self.raw is None:
 93            return
 94        self.data, self.units, self.sanitization = parse(
 95            self.code, self.raw, self.issued
 96        )
 97        if self.data is None or self.units is None:
 98            return
 99        self.translations = translate_taf(self.data, self.units)
100
101    @property
102    def summary(self) -> List[str]:
103        """Condensed summary for each forecast created from translations"""
104        if not self.translations:
105            self.update()
106        if self.translations is None or self.translations.forecast is None:
107            return []
108        return [summary.taf(trans) for trans in self.translations.forecast]
109
110    @property
111    def speech(self) -> Optional[str]:
112        """Report summary designed to be read by a text-to-speech program"""
113        if not self.data:
114            self.update()
115        if self.data is None or self.units is None:
116            return None
117        return speech.taf(self.data, self.units)

The Taf class offers an object-oriented approach to managing TAF data for a single station.

>>> from avwx import Taf
>>> kjfk = Taf("KJFK")
>>> kjfk.station.name
'John F Kennedy International Airport'
>>> kjfk.update()
True
>>> kjfk.last_updated
datetime.datetime(2018, 3, 4, 23, 43, 26, 209644, tzinfo=datetime.timezone.utc)
>>> kjfk.raw
'KJFK 042030Z 0421/0524 33016G27KT P6SM BKN045 FM051600 36016G22KT P6SM BKN040 FM052100 35013KT P6SM SCT035'
>>> len(kjfk.data.forecast)
3
>>> kjfk.data.forecast[0].flight_rules
'VFR'
>>> kjfk.translations.forecast[0].wind
'NNW-330 at 16kt gusting to 27kt'
>>> kjfk.speech
'Starting on March 4th - From 21 to 16 zulu, Winds three three zero at 16kt gusting to 27kt. Visibility greater than six miles. Broken layer at 4500ft. From 16 to 21 zulu, Winds three six zero at 16kt gusting to 22kt. Visibility greater than six miles. Broken layer at 4000ft. From 21 to midnight zulu, Winds three five zero at 13kt. Visibility greater than six miles. Scattered clouds at 3500ft'

The parse and from_report methods can parse a report string if you want to override the normal fetching process.

>>> from avwx import Taf
>>> report = "TAF ZYHB 082300Z 0823/0911 VRB03KT 9999 SCT018 BKN120 TX14/0907Z TN04/0921Z FM090100 09015KT 9999 -SHRA WS020/13045KT SCT018 BKN120 BECMG 0904/0906 34008KT PROB30 TEMPO 0906/0911 7000 -RA SCT020 650104 530804 RMK FCST BASED ON AUTO OBS. NXT FCST BY 090600Z"
>>> zyhb = Taf.from_report(report)
True
>>> zyhb.station.city
'Hulan'
>>> zyhb.data.remarks
'RMK FCST BASED ON AUTO OBS. NXT FCST BY 090600Z'
>>> zyhb.summary[-1]
'Vis 7km, Light Rain, Scattered clouds at 2000ft, Frequent moderate turbulence in clear air from 8000ft to 12000ft, Moderate icing in clouds from 1000ft to 5000ft'
data: Optional[avwx.structs.TafData] = None
translations: Optional[avwx.structs.TafTrans] = None
summary: List[str]
101    @property
102    def summary(self) -> List[str]:
103        """Condensed summary for each forecast created from translations"""
104        if not self.translations:
105            self.update()
106        if self.translations is None or self.translations.forecast is None:
107            return []
108        return [summary.taf(trans) for trans in self.translations.forecast]

Condensed summary for each forecast created from translations

speech: Optional[str]
110    @property
111    def speech(self) -> Optional[str]:
112        """Report summary designed to be read by a text-to-speech program"""
113        if not self.data:
114            self.update()
115        if self.data is None or self.units is None:
116            return None
117        return speech.taf(self.data, self.units)

Report summary designed to be read by a text-to-speech program

Inherited Members
avwx.current.base.Report
Report
sanitization
avwx.base.ManagedReport
code
station
service
from_report
update
async_update
avwx.base.AVWXBase
last_updated
issued
source
raw
units
parse
sanitize