avwx.base
Report parent classes.
1"""Report parent classes.""" 2 3# stdlib 4from __future__ import annotations 5 6import asyncio as aio 7from abc import ABCMeta, abstractmethod 8from contextlib import suppress 9from datetime import date, datetime, timezone 10from typing import TYPE_CHECKING 11 12# module 13from avwx.exceptions import BadStation 14from avwx.station import Station 15 16if TYPE_CHECKING: 17 from avwx.service import Service 18 from avwx.structs import ReportData, Units 19 20try: 21 from typing import Self 22except ImportError: 23 from typing_extensions import Self 24 25 26# Report header words that are never station codes 27_REPORT_HEADERS = {"METAR", "SPECI", "TAF", "COR", "AMD", "RTD"} 28 29 30def find_station(report: str) -> Station | None: 31 """Returns the first Station found in a report string""" 32 for item in report.split(): 33 if item.upper() in _REPORT_HEADERS: 34 continue 35 with suppress(BadStation): 36 return Station.from_code(item.upper()) 37 return None 38 39 40class AVWXBase(metaclass=ABCMeta): 41 """Abstract base class for AVWX report types.""" 42 43 #: UTC datetime object when the report was last updated 44 last_updated: datetime | None = None 45 46 #: UTC date object when the report was issued 47 issued: date | None = None 48 49 #: Root URL used to retrieve the current report 50 source: str | None = None 51 52 #: The original report string 53 raw: str | None = None 54 55 #: ReportData dataclass of parsed data values and units 56 data: ReportData | None = None 57 58 #: Units inferred from the station location and report contents 59 units: Units | None = None 60 61 def __repr__(self) -> str: 62 return f"<avwx.{self.__class__.__name__}>" 63 64 def _set_meta(self) -> None: 65 """Update timestamps after parsing.""" 66 self.last_updated = datetime.now(tz=timezone.utc) 67 with suppress(AttributeError): 68 self.issued = self.data.time.dt.date() # type: ignore 69 70 @abstractmethod 71 def _post_parse(self) -> None: 72 pass 73 74 @classmethod 75 def from_report(cls, report: str, issued: date | None = None) -> Self | None: 76 """Return an updated report object based on an existing report.""" 77 report = report.strip() 78 obj = cls() 79 obj.parse(report, issued=issued) 80 return obj 81 82 def parse(self, report: str, issued: date | None = None) -> bool: 83 """Update report data by parsing a given report. 84 85 Can accept a report issue date if not a recent report string. 86 """ 87 self.source = None 88 if not report or report == self.raw: 89 return False 90 self.raw = report 91 self.issued = issued 92 self._post_parse() 93 self._set_meta() 94 return True 95 96 @staticmethod 97 def sanitize(report: str) -> str: 98 """Sanitize the report string. 99 100 This has not been overridden and returns the raw report. 101 """ 102 return report 103 104 105class ManagedReport(AVWXBase, metaclass=ABCMeta): 106 """Abstract base class for reports types associated with a single station.""" 107 108 #: 4-character station code the report was initialized with 109 code: str | None = None 110 111 #: Provide basic station info if given at init 112 station: Station | None = None 113 114 #: Service object used to fetch the report string 115 service: Service 116 117 def __init__(self, code: str): 118 code = code.upper() 119 self.code = code 120 self.station = Station.from_code(code) 121 122 def __repr__(self) -> str: 123 return f"<avwx.{self.__class__.__name__} code={self.code}>" 124 125 @abstractmethod 126 async def _post_update(self) -> None: 127 pass 128 129 @classmethod 130 def from_report(cls, report: str, issued: date | None = None) -> Self | None: 131 """Return an updated report object based on an existing report.""" 132 report = report.strip() 133 station = find_station(report) 134 if not station: 135 return None 136 obj = cls(station.lookup_code) 137 obj.parse(report, issued=issued) 138 return obj 139 140 async def _update(self, report: str | list[str], issued: date | None, *, disable_post: bool) -> bool: 141 if not report or report == self.raw: 142 return False 143 self.raw = report # type: ignore 144 self.issued = issued 145 if not disable_post: 146 await self._post_update() 147 self._set_meta() 148 return True 149 150 def update(self, timeout: int = 10, *, disable_post: bool = False) -> bool: 151 """Update. report data by fetching and parsing the report. 152 153 Returns True if a new report is available, else False. 154 """ 155 report = self.service.fetch(self.code, timeout=timeout) # type: ignore 156 self.source = self.service.root 157 return aio.run(self._update(report, None, disable_post=disable_post)) 158 159 async def async_update(self, timeout: int = 10, *, disable_post: bool = False) -> bool: 160 """Async update report data by fetching and parsing the report. 161 162 Returns True if a new report is available, else False. 163 """ 164 report = await self.service.async_fetch(self.code, timeout=timeout) # type: ignore 165 self.source = self.service.root 166 return await self._update(report, None, disable_post=disable_post)
31def find_station(report: str) -> Station | None: 32 """Returns the first Station found in a report string""" 33 for item in report.split(): 34 if item.upper() in _REPORT_HEADERS: 35 continue 36 with suppress(BadStation): 37 return Station.from_code(item.upper()) 38 return None
Returns the first Station found in a report string
class
AVWXBase:
41class AVWXBase(metaclass=ABCMeta): 42 """Abstract base class for AVWX report types.""" 43 44 #: UTC datetime object when the report was last updated 45 last_updated: datetime | None = None 46 47 #: UTC date object when the report was issued 48 issued: date | None = None 49 50 #: Root URL used to retrieve the current report 51 source: str | None = None 52 53 #: The original report string 54 raw: str | None = None 55 56 #: ReportData dataclass of parsed data values and units 57 data: ReportData | None = None 58 59 #: Units inferred from the station location and report contents 60 units: Units | None = None 61 62 def __repr__(self) -> str: 63 return f"<avwx.{self.__class__.__name__}>" 64 65 def _set_meta(self) -> None: 66 """Update timestamps after parsing.""" 67 self.last_updated = datetime.now(tz=timezone.utc) 68 with suppress(AttributeError): 69 self.issued = self.data.time.dt.date() # type: ignore 70 71 @abstractmethod 72 def _post_parse(self) -> None: 73 pass 74 75 @classmethod 76 def from_report(cls, report: str, issued: date | None = None) -> Self | None: 77 """Return an updated report object based on an existing report.""" 78 report = report.strip() 79 obj = cls() 80 obj.parse(report, issued=issued) 81 return obj 82 83 def parse(self, report: str, issued: date | None = None) -> bool: 84 """Update report data by parsing a given report. 85 86 Can accept a report issue date if not a recent report string. 87 """ 88 self.source = None 89 if not report or report == self.raw: 90 return False 91 self.raw = report 92 self.issued = issued 93 self._post_parse() 94 self._set_meta() 95 return True 96 97 @staticmethod 98 def sanitize(report: str) -> str: 99 """Sanitize the report string. 100 101 This has not been overridden and returns the raw report. 102 """ 103 return report
Abstract base class for AVWX report types.
@classmethod
def
from_report(cls, report: str, issued: datetime.date | None = None) -> Optional[Self]:
75 @classmethod 76 def from_report(cls, report: str, issued: date | None = None) -> Self | None: 77 """Return an updated report object based on an existing report.""" 78 report = report.strip() 79 obj = cls() 80 obj.parse(report, issued=issued) 81 return obj
Return an updated report object based on an existing report.
def
parse(self, report: str, issued: datetime.date | None = None) -> bool:
83 def parse(self, report: str, issued: date | None = None) -> bool: 84 """Update report data by parsing a given report. 85 86 Can accept a report issue date if not a recent report string. 87 """ 88 self.source = None 89 if not report or report == self.raw: 90 return False 91 self.raw = report 92 self.issued = issued 93 self._post_parse() 94 self._set_meta() 95 return True
Update report data by parsing a given report.
Can accept a report issue date if not a recent report string.
@staticmethod
def
sanitize(report: str) -> str:
97 @staticmethod 98 def sanitize(report: str) -> str: 99 """Sanitize the report string. 100 101 This has not been overridden and returns the raw report. 102 """ 103 return report
Sanitize the report string.
This has not been overridden and returns the raw report.
106class ManagedReport(AVWXBase, metaclass=ABCMeta): 107 """Abstract base class for reports types associated with a single station.""" 108 109 #: 4-character station code the report was initialized with 110 code: str | None = None 111 112 #: Provide basic station info if given at init 113 station: Station | None = None 114 115 #: Service object used to fetch the report string 116 service: Service 117 118 def __init__(self, code: str): 119 code = code.upper() 120 self.code = code 121 self.station = Station.from_code(code) 122 123 def __repr__(self) -> str: 124 return f"<avwx.{self.__class__.__name__} code={self.code}>" 125 126 @abstractmethod 127 async def _post_update(self) -> None: 128 pass 129 130 @classmethod 131 def from_report(cls, report: str, issued: date | None = None) -> Self | None: 132 """Return an updated report object based on an existing report.""" 133 report = report.strip() 134 station = find_station(report) 135 if not station: 136 return None 137 obj = cls(station.lookup_code) 138 obj.parse(report, issued=issued) 139 return obj 140 141 async def _update(self, report: str | list[str], issued: date | None, *, disable_post: bool) -> bool: 142 if not report or report == self.raw: 143 return False 144 self.raw = report # type: ignore 145 self.issued = issued 146 if not disable_post: 147 await self._post_update() 148 self._set_meta() 149 return True 150 151 def update(self, timeout: int = 10, *, disable_post: bool = False) -> bool: 152 """Update. report data by fetching and parsing the report. 153 154 Returns True if a new report is available, else False. 155 """ 156 report = self.service.fetch(self.code, timeout=timeout) # type: ignore 157 self.source = self.service.root 158 return aio.run(self._update(report, None, disable_post=disable_post)) 159 160 async def async_update(self, timeout: int = 10, *, disable_post: bool = False) -> bool: 161 """Async update report data by fetching and parsing the report. 162 163 Returns True if a new report is available, else False. 164 """ 165 report = await self.service.async_fetch(self.code, timeout=timeout) # type: ignore 166 self.source = self.service.root 167 return await self._update(report, None, disable_post=disable_post)
Abstract base class for reports types associated with a single station.
service: avwx.service.Service
@classmethod
def
from_report(cls, report: str, issued: datetime.date | None = None) -> Optional[Self]:
130 @classmethod 131 def from_report(cls, report: str, issued: date | None = None) -> Self | None: 132 """Return an updated report object based on an existing report.""" 133 report = report.strip() 134 station = find_station(report) 135 if not station: 136 return None 137 obj = cls(station.lookup_code) 138 obj.parse(report, issued=issued) 139 return obj
Return an updated report object based on an existing report.
def
update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
151 def update(self, timeout: int = 10, *, disable_post: bool = False) -> bool: 152 """Update. report data by fetching and parsing the report. 153 154 Returns True if a new report is available, else False. 155 """ 156 report = self.service.fetch(self.code, timeout=timeout) # type: ignore 157 self.source = self.service.root 158 return aio.run(self._update(report, None, disable_post=disable_post))
Update. report data by fetching and parsing the report.
Returns True if a new report is available, else False.
async def
async_update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
160 async def async_update(self, timeout: int = 10, *, disable_post: bool = False) -> bool: 161 """Async update report data by fetching and parsing the report. 162 163 Returns True if a new report is available, else False. 164 """ 165 report = await self.service.async_fetch(self.code, timeout=timeout) # type: ignore 166 self.source = self.service.root 167 return await self._update(report, None, disable_post=disable_post)
Async update report data by fetching and parsing the report.
Returns True if a new report is available, else False.