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

Returns the first Station found in a report string

class AVWXBase:
35class AVWXBase(metaclass=ABCMeta):
36    """Abstract base class for AVWX report types."""
37
38    #: UTC datetime object when the report was last updated
39    last_updated: datetime | None = None
40
41    #: UTC date object when the report was issued
42    issued: date | None = None
43
44    #: Root URL used to retrieve the current report
45    source: str | None = None
46
47    #: The original report string
48    raw: str | None = None
49
50    #: ReportData dataclass of parsed data values and units
51    data: ReportData | None = None
52
53    #: Units inferred from the station location and report contents
54    units: Units | None = None
55
56    def __repr__(self) -> str:
57        return f"<avwx.{self.__class__.__name__}>"
58
59    def _set_meta(self) -> None:
60        """Update timestamps after parsing."""
61        self.last_updated = datetime.now(tz=timezone.utc)
62        with suppress(AttributeError):
63            self.issued = self.data.time.dt.date()  # type: ignore
64
65    @abstractmethod
66    def _post_parse(self) -> None:
67        pass
68
69    @classmethod
70    def from_report(cls, report: str, issued: date | None = None) -> Self | None:
71        """Return an updated report object based on an existing report."""
72        report = report.strip()
73        obj = cls()
74        obj.parse(report, issued=issued)
75        return obj
76
77    def parse(self, report: str, issued: date | None = None) -> bool:
78        """Update report data by parsing a given report.
79
80        Can accept a report issue date if not a recent report string.
81        """
82        self.source = None
83        if not report or report == self.raw:
84            return False
85        self.raw = report
86        self.issued = issued
87        self._post_parse()
88        self._set_meta()
89        return True
90
91    @staticmethod
92    def sanitize(report: str) -> str:
93        """Sanitize the report string.
94
95        This has not been overridden and returns the raw report.
96        """
97        return report

Abstract base class for AVWX report types.

last_updated: datetime.datetime | None = None
issued: datetime.date | None = None
source: str | None = None
raw: str | None = None
data: avwx.structs.ReportData | None = None
units: avwx.structs.Units | None = None
@classmethod
def from_report(cls, report: str, issued: datetime.date | None = None) -> Optional[Self]:
69    @classmethod
70    def from_report(cls, report: str, issued: date | None = None) -> Self | None:
71        """Return an updated report object based on an existing report."""
72        report = report.strip()
73        obj = cls()
74        obj.parse(report, issued=issued)
75        return obj

Return an updated report object based on an existing report.

def parse(self, report: str, issued: datetime.date | None = None) -> bool:
77    def parse(self, report: str, issued: date | None = None) -> bool:
78        """Update report data by parsing a given report.
79
80        Can accept a report issue date if not a recent report string.
81        """
82        self.source = None
83        if not report or report == self.raw:
84            return False
85        self.raw = report
86        self.issued = issued
87        self._post_parse()
88        self._set_meta()
89        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:
91    @staticmethod
92    def sanitize(report: str) -> str:
93        """Sanitize the report string.
94
95        This has not been overridden and returns the raw report.
96        """
97        return report

Sanitize the report string.

This has not been overridden and returns the raw report.

class ManagedReport(AVWXBase):
100class ManagedReport(AVWXBase, metaclass=ABCMeta):
101    """Abstract base class for reports types associated with a single station."""
102
103    #: 4-character station code the report was initialized with
104    code: str | None = None
105
106    #: Provide basic station info if given at init
107    station: Station | None = None
108
109    #: Service object used to fetch the report string
110    service: Service
111
112    def __init__(self, code: str):
113        code = code.upper()
114        self.code = code
115        self.station = Station.from_code(code)
116
117    def __repr__(self) -> str:
118        return f"<avwx.{self.__class__.__name__} code={self.code}>"
119
120    @abstractmethod
121    async def _post_update(self) -> None:
122        pass
123
124    @classmethod
125    def from_report(cls, report: str, issued: date | None = None) -> Self | None:
126        """Return an updated report object based on an existing report."""
127        report = report.strip()
128        station = find_station(report)
129        if not station:
130            return None
131        obj = cls(station.lookup_code)
132        obj.parse(report, issued=issued)
133        return obj
134
135    async def _update(self, report: str | list[str], issued: date | None, *, disable_post: bool) -> bool:
136        if not report or report == self.raw:
137            return False
138        self.raw = report  # type: ignore
139        self.issued = issued
140        if not disable_post:
141            await self._post_update()
142        self._set_meta()
143        return True
144
145    def update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
146        """Update. report data by fetching and parsing the report.
147
148        Returns True if a new report is available, else False.
149        """
150        report = self.service.fetch(self.code, timeout=timeout)  # type: ignore
151        self.source = self.service.root
152        return aio.run(self._update(report, None, disable_post=disable_post))
153
154    async def async_update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
155        """Async update report data by fetching and parsing the report.
156
157        Returns True if a new report is available, else False.
158        """
159        report = await self.service.async_fetch(self.code, timeout=timeout)  # type: ignore
160        self.source = self.service.root
161        return await self._update(report, None, disable_post=disable_post)

Abstract base class for reports types associated with a single station.

code: str | None = None
station: avwx.station.Station | None = None
@classmethod
def from_report(cls, report: str, issued: datetime.date | None = None) -> Optional[Self]:
124    @classmethod
125    def from_report(cls, report: str, issued: date | None = None) -> Self | None:
126        """Return an updated report object based on an existing report."""
127        report = report.strip()
128        station = find_station(report)
129        if not station:
130            return None
131        obj = cls(station.lookup_code)
132        obj.parse(report, issued=issued)
133        return obj

Return an updated report object based on an existing report.

def update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
145    def update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
146        """Update. report data by fetching and parsing the report.
147
148        Returns True if a new report is available, else False.
149        """
150        report = self.service.fetch(self.code, timeout=timeout)  # type: ignore
151        self.source = self.service.root
152        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:
154    async def async_update(self, timeout: int = 10, *, disable_post: bool = False) -> bool:
155        """Async update report data by fetching and parsing the report.
156
157        Returns True if a new report is available, else False.
158        """
159        report = await self.service.async_fetch(self.code, timeout=timeout)  # type: ignore
160        self.source = self.service.root
161        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.