avwx.structs
Contains dataclasses to hold report data
1""" 2Contains dataclasses to hold report data 3""" 4 5# pylint: disable=missing-class-docstring,missing-function-docstring,too-many-instance-attributes 6 7# stdlib 8from dataclasses import dataclass, field 9from datetime import datetime 10from typing import Dict, List, Optional, Tuple, Type, TypeVar, Union 11 12# module 13from avwx.load_utils import LazyLoad 14from avwx.static.core import IN_UNITS, NA_UNITS 15 16 17try: 18 from shapely.geometry import Point, Polygon # type: ignore 19except ModuleNotFoundError: 20 Point, Polygon = None, None 21 22AIRCRAFT = LazyLoad("aircraft") 23 24 25@dataclass 26class Aircraft: 27 code: str 28 type: str 29 30 @classmethod 31 def from_icao(cls, code: str) -> "Aircraft": 32 """Load an Aircraft from an ICAO aircraft code""" 33 try: 34 return cls(code=code, type=AIRCRAFT[code]) 35 except KeyError as key_error: 36 msg = f"{code} is not a known aircraft code" 37 raise ValueError(msg) from key_error 38 39 40@dataclass 41class Units: 42 accumulation: str 43 altimeter: str 44 altitude: str 45 temperature: str 46 visibility: str 47 wind_speed: str 48 49 @classmethod 50 def international(cls) -> "Units": 51 """Create default internation units""" 52 return cls(**IN_UNITS) 53 54 @classmethod 55 def north_american(cls) -> "Units": 56 """Create default North American units""" 57 return cls(**NA_UNITS) 58 59 60@dataclass 61class Number: 62 repr: str 63 value: Union[int, float, None] 64 spoken: str 65 66 67@dataclass 68class Fraction(Number): 69 numerator: int 70 denominator: int 71 normalized: str 72 73 74@dataclass 75class Timestamp: 76 # pylint: disable=invalid-name 77 repr: str 78 dt: Optional[datetime] 79 80 81CodeType = TypeVar("CodeType", bound="Code") # pylint: disable=invalid-name 82 83 84@dataclass 85class Code: 86 repr: str 87 value: str 88 89 @classmethod 90 def from_dict( 91 cls: Type[CodeType], 92 key: Optional[str], 93 codes: Dict[str, str], 94 default: Optional[str] = None, 95 error: bool = True, 96 ) -> Optional[CodeType]: 97 """Load a code from a known key and value dict""" 98 value: Optional[str] 99 if not key: 100 return None 101 try: 102 value = codes[key] 103 except KeyError as exc: 104 if error: 105 raise KeyError(f"No code found for {key}") from exc 106 value = default 107 return cls(key, value or "Unknown") 108 109 @classmethod 110 def from_list( 111 cls: Type[CodeType], 112 keys: Optional[str], 113 codes: Dict[str, str], 114 exclusive: bool = False, 115 ) -> List[CodeType]: 116 """Load a list of codes from string characters""" 117 if not keys: 118 return [] 119 out = [] 120 for key in keys.strip(): 121 if value := codes.get(key): 122 out.append(cls(key, value)) 123 elif exclusive: 124 return [] 125 return out 126 127 128@dataclass 129class Coord: 130 lat: float 131 lon: float 132 repr: Optional[str] = None 133 134 @property 135 def pair(self) -> Tuple[float, float]: 136 return self.lat, self.lon 137 138 @property 139 def point(self) -> Point: 140 if Point is None: 141 raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature") 142 return Point(self.lat, self.lon) 143 144 @staticmethod 145 def to_dms(value: float) -> Tuple[int, int, int]: 146 """Convert a coordinate decimal value to degree, minute, second""" 147 minute, second = divmod(abs(value) * 3600, 60) 148 degree, minute = divmod(minute, 60) 149 if value < 0: 150 degree *= -1 151 return int(degree), int(minute), int(second) 152 153 154@dataclass 155class Cloud: 156 repr: str 157 type: Optional[str] = None 158 base: Optional[int] = None 159 top: Optional[int] = None 160 modifier: Optional[str] = None 161 162 163@dataclass 164class RunwayVisibility: 165 repr: str 166 runway: str 167 visibility: Optional[Number] 168 variable_visibility: List[Number] 169 trend: Optional[Code] 170 171 172@dataclass 173class Location: 174 repr: str 175 station: Optional[str] 176 direction: Optional[Number] 177 distance: Optional[Number] 178 179 180@dataclass 181class PressureTendency: 182 repr: str 183 tendency: str 184 change: float 185 186 187@dataclass 188class FiveDigitCodes: 189 maximum_temperature_6: Optional[Number] = None # 1 190 minimum_temperature_6: Optional[Number] = None # 2 191 pressure_tendency: Optional[PressureTendency] = None # 5 192 precip_36_hours: Optional[Number] = None # 6 193 precip_24_hours: Optional[Number] = None # 7 194 sunshine_minutes: Optional[Number] = None # 9 195 196 197@dataclass 198class RemarksData(FiveDigitCodes): 199 codes: List[Code] = field(default_factory=[]) # type: ignore 200 dewpoint_decimal: Optional[Number] = None 201 maximum_temperature_24: Optional[Number] = None 202 minimum_temperature_24: Optional[Number] = None 203 precip_hourly: Optional[Number] = None 204 sea_level_pressure: Optional[Number] = None 205 snow_depth: Optional[Number] = None 206 temperature_decimal: Optional[Number] = None 207 208 209@dataclass 210class ReportData: 211 raw: str 212 sanitized: str 213 station: Optional[str] 214 time: Optional[Timestamp] 215 remarks: Optional[str] 216 217 218@dataclass 219class SharedData: 220 altimeter: Optional[Number] 221 clouds: List[Cloud] 222 flight_rules: str 223 other: List[str] 224 visibility: Optional[Number] 225 wind_direction: Optional[Number] 226 wind_gust: Optional[Number] 227 wind_speed: Optional[Number] 228 wx_codes: List[Code] 229 230 231@dataclass 232class MetarData(ReportData, SharedData): 233 dewpoint: Optional[Number] 234 relative_humidity: Optional[float] 235 remarks_info: Optional[RemarksData] 236 runway_visibility: List[RunwayVisibility] 237 temperature: Optional[Number] 238 wind_variable_direction: List[Number] 239 density_altitude: Optional[int] = None 240 pressure_altitude: Optional[int] = None 241 242 243@dataclass 244class TafLineData(SharedData): 245 end_time: Optional[Timestamp] 246 icing: List[str] 247 probability: Optional[Number] 248 raw: str 249 sanitized: str 250 start_time: Optional[Timestamp] 251 transition_start: Optional[Timestamp] 252 turbulence: List[str] 253 type: str 254 wind_shear: Optional[str] 255 wind_variable_direction: Optional[List[Number]] 256 257 258@dataclass 259class TafData(ReportData): 260 forecast: List[TafLineData] 261 start_time: Optional[Timestamp] 262 end_time: Optional[Timestamp] 263 max_temp: Optional[str] = None 264 min_temp: Optional[str] = None 265 alts: Optional[List[str]] = None 266 temps: Optional[List[str]] = None 267 remarks_info: Optional[RemarksData] = None 268 269 270@dataclass 271class ReportTrans: 272 altimeter: str 273 clouds: str 274 wx_codes: str 275 visibility: str 276 277 278@dataclass 279class MetarTrans(ReportTrans): 280 dewpoint: str 281 remarks: dict 282 temperature: str 283 wind: str 284 285 286@dataclass 287class TafLineTrans(ReportTrans): 288 icing: str 289 turbulence: str 290 wind: str 291 wind_shear: str 292 293 294@dataclass 295class TafTrans: 296 forecast: List[TafLineTrans] 297 max_temp: str 298 min_temp: str 299 remarks: dict 300 301 302@dataclass 303class Turbulence: 304 severity: str 305 floor: Optional[Number] = None 306 ceiling: Optional[Number] = None 307 308 309@dataclass 310class Icing(Turbulence): 311 type: Optional[str] = None 312 313 314@dataclass 315class PirepData(ReportData): 316 # pylint: disable=invalid-name 317 aircraft: Union[Aircraft, str, None] = None 318 altitude: Union[Number, str, None] = None 319 clouds: Optional[List[Cloud]] = None 320 flight_visibility: Optional[Number] = None 321 icing: Optional[Icing] = None 322 location: Optional[Location] = None 323 other: Optional[List[str]] = None 324 temperature: Optional[Number] = None 325 turbulence: Optional[Turbulence] = None 326 type: Optional[str] = None 327 wx_codes: Optional[List[Code]] = None 328 329 330@dataclass 331class AirepData(ReportData): 332 pass 333 334 335@dataclass 336class Bulletin: 337 repr: str 338 type: Code 339 country: str 340 number: int 341 342 343@dataclass 344class Movement: 345 repr: str 346 direction: Optional[Number] 347 speed: Optional[Number] 348 349 350@dataclass 351class AirSigObservation: 352 type: Optional[Code] 353 start_time: Optional[Timestamp] 354 end_time: Optional[Timestamp] 355 position: Optional[Coord] 356 floor: Optional[Number] 357 ceiling: Optional[Number] 358 coords: List[Coord] 359 bounds: List[str] 360 movement: Optional[Movement] 361 intensity: Optional[Code] 362 other: List[str] 363 364 @property 365 def poly(self) -> Optional[Polygon]: 366 if Polygon is None: 367 raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature") 368 return Polygon([c.pair for c in self.coords]) if len(self.coords) > 2 else None 369 370 371@dataclass 372class AirSigmetData(ReportData): 373 bulletin: Bulletin 374 issuer: str 375 correction: Optional[str] 376 area: str 377 type: str 378 start_time: Optional[Timestamp] 379 end_time: Optional[Timestamp] 380 body: str 381 region: str 382 observation: Optional[AirSigObservation] 383 forecast: Optional[AirSigObservation] 384 385 386@dataclass 387class Qualifiers: 388 repr: str 389 fir: str 390 subject: Optional[Code] 391 condition: Optional[Code] 392 traffic: Optional[Code] 393 purpose: List[Code] 394 scope: List[Code] 395 lower: Optional[Number] 396 upper: Optional[Number] 397 coord: Optional[Coord] 398 radius: Optional[Number] 399 400 401@dataclass 402class NotamData(ReportData): 403 number: Optional[str] 404 replaces: Optional[str] 405 type: Optional[Code] 406 qualifiers: Optional[Qualifiers] 407 start_time: Union[Timestamp, Code, None] 408 end_time: Union[Timestamp, Code, None] 409 schedule: Optional[str] 410 body: str 411 lower: Optional[Number] 412 upper: Optional[Number] 413 414 415@dataclass 416class GfsPeriod: 417 time: Timestamp 418 temperature: Number 419 dewpoint: Number 420 cloud: Code 421 temperature_minmax: Optional[Number] = None 422 precip_chance_12: Optional[Number] = None 423 precip_amount_12: Optional[Code] = None 424 thunderstorm_12: Optional[Number] = None 425 severe_storm_12: Optional[Number] = None 426 freezing_precip: Optional[Number] = None 427 precip_type: Optional[Code] = None 428 snow: Optional[Number] = None 429 430 431@dataclass 432class MavPeriod(GfsPeriod): 433 wind_direction: Optional[Number] = None 434 wind_speed: Optional[Number] = None 435 precip_chance_6: Optional[Number] = None 436 precip_amount_6: Optional[Code] = None 437 thunderstorm_6: Optional[Number] = None 438 severe_storm_6: Optional[Number] = None 439 ceiling: Optional[Code] = None 440 visibility: Optional[Code] = None 441 vis_obstruction: Optional[Code] = None 442 443 444@dataclass 445class MexPeriod(GfsPeriod): 446 precip_chance_24: Optional[Number] = None 447 precip_amount_24: Optional[Code] = None 448 thunderstorm_24: Optional[Number] = None 449 severe_storm_24: Optional[Number] = None 450 rain_snow_mix: Optional[Number] = None 451 snow_amount_24: Optional[Code] = None 452 453 454@dataclass 455class MavData(ReportData): 456 forecast: List[MavPeriod] 457 458 459@dataclass 460class MexData(ReportData): 461 forecast: List[MexPeriod] 462 463 464@dataclass 465class NbmUnits(Units): 466 duration: str 467 solar_radiation: str 468 wave_height: str 469 470 471@dataclass 472class NbmPeriod: 473 time: Timestamp 474 temperature: Optional[Number] = None 475 dewpoint: Optional[Number] = None 476 sky_cover: Optional[Number] = None 477 wind_direction: Optional[Number] = None 478 wind_speed: Optional[Number] = None 479 wind_gust: Optional[Number] = None 480 snow_level: Optional[Number] = None 481 precip_duration: Optional[Number] = None 482 freezing_precip: Optional[Number] = None 483 snow: Optional[Number] = None 484 sleet: Optional[Number] = None 485 rain: Optional[Number] = None 486 solar_radiation: Optional[Number] = None 487 wave_height: Optional[Number] = None 488 489 490@dataclass 491class NbhsShared(NbmPeriod): 492 ceiling: Optional[Number] = None 493 visibility: Optional[Number] = None 494 cloud_base: Optional[Number] = None 495 mixing_height: Optional[Number] = None 496 transport_wind_direction: Optional[Number] = None 497 transport_wind_speed: Optional[Number] = None 498 haines: Optional[List[Number]] = None 499 500 501@dataclass 502class NbhPeriod(NbhsShared): 503 precip_chance_1: Optional[Number] = None 504 precip_chance_6: Optional[Number] = None 505 precip_amount_1: Optional[Number] = None 506 thunderstorm_1: Optional[Number] = None 507 snow_amount_1: Optional[Number] = None 508 icing_amount_1: Optional[Number] = None 509 510 511@dataclass 512class NbsPeriod(NbhsShared): 513 temperature_minmax: Optional[Number] = None 514 precip_chance_6: Optional[Number] = None 515 precip_chance_12: Optional[Number] = None 516 precip_amount_6: Optional[Number] = None 517 precip_amount_12: Optional[Number] = None 518 precip_duration: Optional[Number] = None 519 thunderstorm_3: Optional[Number] = None 520 thunderstorm_6: Optional[Number] = None 521 thunderstorm_12: Optional[Number] = None 522 snow_amount_6: Optional[Number] = None 523 icing_amount_6: Optional[Number] = None 524 525 526@dataclass 527class NbePeriod(NbmPeriod): 528 temperature_minmax: Optional[Number] = None 529 precip_chance_12: Optional[Number] = None 530 precip_amount_12: Optional[Number] = None 531 precip_amount_24: Optional[Number] = None 532 thunderstorm_12: Optional[Number] = None 533 snow_amount_12: Optional[Number] = None 534 snow_amount_24: Optional[Number] = None 535 icing_amount_12: Optional[Number] = None 536 537 538@dataclass 539class NbxPeriod(NbmPeriod): 540 precip_chance_12: Optional[Number] = None 541 precip_amount_12: Optional[Number] = None 542 precip_amount_24: Optional[Number] = None 543 snow_amount_12: Optional[Number] = None 544 icing_amount_12: Optional[Number] = None 545 546 547@dataclass 548class NbhData(ReportData): 549 forecast: List[NbhPeriod] 550 551 552@dataclass 553class NbsData(ReportData): 554 forecast: List[NbsPeriod] 555 556 557@dataclass 558class NbeData(ReportData): 559 forecast: List[NbePeriod] 560 561 562@dataclass 563class NbxData(ReportData): 564 forecast: List[NbxPeriod] 565 566 567# @dataclass 568# class GfsPeriodTrans: 569# temperature: str 570# dewpoint: str 571# cloud: str 572# precip_chance_12: str 573# precip_amount_12: str 574# thunderstorm_12: str 575# severe_storm_12: str 576# freezing_precip: str 577# precip_type: str 578# snow: str 579 580 581# @dataclass 582# class MavPeriodTrans(GfsPeriodTrans): 583# wind_direction: str 584# wind_speed: str 585# precip_chance_6: str 586# precip_amount_6: str 587# thunderstorm_6: str 588# severe_storm_6: str 589# ceiling: str 590# visibility: str 591# vis_obstruction: str 592 593 594# @dataclass 595# class MexPeriodTrans(GfsPeriodTrans): 596# precip_chance_24: str 597# precip_amount_24: str 598# thunderstorm_24: str 599# severe_storm_24: str 600# rain_snow_mix: str 601# snow_amount_24: str 602 603 604@dataclass 605class Sanitization: 606 removed: List[str] = field(default_factory=list) 607 replaced: Dict[str, str] = field(default_factory=dict) 608 duplicates_found: bool = False 609 extra_spaces_found: bool = False 610 extra_spaces_needed: bool = False 611 612 @property 613 def errors_found(self) -> bool: 614 return bool( 615 self.removed 616 or self.replaced 617 or self.duplicates_found 618 or self.extra_spaces_found 619 or self.extra_spaces_needed 620 ) 621 622 def log(self, item: str, replacement: Optional[str] = None) -> None: 623 """Log a changed item. Calling without a replacement assumes removal""" 624 item = item.strip() 625 if not item: 626 return 627 if replacement is None: 628 self.removed.insert(0, item) 629 return 630 replacement = replacement.strip() 631 if not replacement: 632 self.removed.insert(0, item) 633 elif item != replacement: 634 self.replaced[item] = replacement 635 636 def log_list(self, before: List[str], after: List[str]) -> None: 637 """Log list differences. Assumes that list length and order haven't changed""" 638 for item, replacement in zip(before, after): 639 if item != replacement: 640 self.log(item, replacement)
AIRCRAFT =
<avwx.load_utils.LazyLoad object>
@dataclass
class
Aircraft:
26@dataclass 27class Aircraft: 28 code: str 29 type: str 30 31 @classmethod 32 def from_icao(cls, code: str) -> "Aircraft": 33 """Load an Aircraft from an ICAO aircraft code""" 34 try: 35 return cls(code=code, type=AIRCRAFT[code]) 36 except KeyError as key_error: 37 msg = f"{code} is not a known aircraft code" 38 raise ValueError(msg) from key_error
31 @classmethod 32 def from_icao(cls, code: str) -> "Aircraft": 33 """Load an Aircraft from an ICAO aircraft code""" 34 try: 35 return cls(code=code, type=AIRCRAFT[code]) 36 except KeyError as key_error: 37 msg = f"{code} is not a known aircraft code" 38 raise ValueError(msg) from key_error
Load an Aircraft from an ICAO aircraft code
@dataclass
class
Units:
41@dataclass 42class Units: 43 accumulation: str 44 altimeter: str 45 altitude: str 46 temperature: str 47 visibility: str 48 wind_speed: str 49 50 @classmethod 51 def international(cls) -> "Units": 52 """Create default internation units""" 53 return cls(**IN_UNITS) 54 55 @classmethod 56 def north_american(cls) -> "Units": 57 """Create default North American units""" 58 return cls(**NA_UNITS)
Units( accumulation: str, altimeter: str, altitude: str, temperature: str, visibility: str, wind_speed: str)
@dataclass
class
Number:
@dataclass
class
Timestamp:
@dataclass
class
Code:
85@dataclass 86class Code: 87 repr: str 88 value: str 89 90 @classmethod 91 def from_dict( 92 cls: Type[CodeType], 93 key: Optional[str], 94 codes: Dict[str, str], 95 default: Optional[str] = None, 96 error: bool = True, 97 ) -> Optional[CodeType]: 98 """Load a code from a known key and value dict""" 99 value: Optional[str] 100 if not key: 101 return None 102 try: 103 value = codes[key] 104 except KeyError as exc: 105 if error: 106 raise KeyError(f"No code found for {key}") from exc 107 value = default 108 return cls(key, value or "Unknown") 109 110 @classmethod 111 def from_list( 112 cls: Type[CodeType], 113 keys: Optional[str], 114 codes: Dict[str, str], 115 exclusive: bool = False, 116 ) -> List[CodeType]: 117 """Load a list of codes from string characters""" 118 if not keys: 119 return [] 120 out = [] 121 for key in keys.strip(): 122 if value := codes.get(key): 123 out.append(cls(key, value)) 124 elif exclusive: 125 return [] 126 return out
@classmethod
def
from_dict( cls: Type[~CodeType], key: Optional[str], codes: Dict[str, str], default: Optional[str] = None, error: bool = True) -> Optional[~CodeType]:
90 @classmethod 91 def from_dict( 92 cls: Type[CodeType], 93 key: Optional[str], 94 codes: Dict[str, str], 95 default: Optional[str] = None, 96 error: bool = True, 97 ) -> Optional[CodeType]: 98 """Load a code from a known key and value dict""" 99 value: Optional[str] 100 if not key: 101 return None 102 try: 103 value = codes[key] 104 except KeyError as exc: 105 if error: 106 raise KeyError(f"No code found for {key}") from exc 107 value = default 108 return cls(key, value or "Unknown")
Load a code from a known key and value dict
@classmethod
def
from_list( cls: Type[~CodeType], keys: Optional[str], codes: Dict[str, str], exclusive: bool = False) -> List[~CodeType]:
110 @classmethod 111 def from_list( 112 cls: Type[CodeType], 113 keys: Optional[str], 114 codes: Dict[str, str], 115 exclusive: bool = False, 116 ) -> List[CodeType]: 117 """Load a list of codes from string characters""" 118 if not keys: 119 return [] 120 out = [] 121 for key in keys.strip(): 122 if value := codes.get(key): 123 out.append(cls(key, value)) 124 elif exclusive: 125 return [] 126 return out
Load a list of codes from string characters
@dataclass
class
Coord:
129@dataclass 130class Coord: 131 lat: float 132 lon: float 133 repr: Optional[str] = None 134 135 @property 136 def pair(self) -> Tuple[float, float]: 137 return self.lat, self.lon 138 139 @property 140 def point(self) -> Point: 141 if Point is None: 142 raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature") 143 return Point(self.lat, self.lon) 144 145 @staticmethod 146 def to_dms(value: float) -> Tuple[int, int, int]: 147 """Convert a coordinate decimal value to degree, minute, second""" 148 minute, second = divmod(abs(value) * 3600, 60) 149 degree, minute = divmod(minute, 60) 150 if value < 0: 151 degree *= -1 152 return int(degree), int(minute), int(second)
@staticmethod
def
to_dms(value: float) -> Tuple[int, int, int]:
145 @staticmethod 146 def to_dms(value: float) -> Tuple[int, int, int]: 147 """Convert a coordinate decimal value to degree, minute, second""" 148 minute, second = divmod(abs(value) * 3600, 60) 149 degree, minute = divmod(minute, 60) 150 if value < 0: 151 degree *= -1 152 return int(degree), int(minute), int(second)
Convert a coordinate decimal value to degree, minute, second
@dataclass
class
Cloud:
155@dataclass 156class Cloud: 157 repr: str 158 type: Optional[str] = None 159 base: Optional[int] = None 160 top: Optional[int] = None 161 modifier: Optional[str] = None
@dataclass
class
RunwayVisibility:
164@dataclass 165class RunwayVisibility: 166 repr: str 167 runway: str 168 visibility: Optional[Number] 169 variable_visibility: List[Number] 170 trend: Optional[Code]
RunwayVisibility( repr: str, runway: str, visibility: Optional[Number], variable_visibility: List[Number], trend: Optional[Code])
visibility: Optional[Number]
variable_visibility: List[Number]
trend: Optional[Code]
@dataclass
class
Location:
173@dataclass 174class Location: 175 repr: str 176 station: Optional[str] 177 direction: Optional[Number] 178 distance: Optional[Number]
Location( repr: str, station: Optional[str], direction: Optional[Number], distance: Optional[Number])
direction: Optional[Number]
distance: Optional[Number]
@dataclass
class
PressureTendency:
@dataclass
class
FiveDigitCodes:
188@dataclass 189class FiveDigitCodes: 190 maximum_temperature_6: Optional[Number] = None # 1 191 minimum_temperature_6: Optional[Number] = None # 2 192 pressure_tendency: Optional[PressureTendency] = None # 5 193 precip_36_hours: Optional[Number] = None # 6 194 precip_24_hours: Optional[Number] = None # 7 195 sunshine_minutes: Optional[Number] = None # 9
FiveDigitCodes( maximum_temperature_6: Optional[Number] = None, minimum_temperature_6: Optional[Number] = None, pressure_tendency: Optional[PressureTendency] = None, precip_36_hours: Optional[Number] = None, precip_24_hours: Optional[Number] = None, sunshine_minutes: Optional[Number] = None)
198@dataclass 199class RemarksData(FiveDigitCodes): 200 codes: List[Code] = field(default_factory=[]) # type: ignore 201 dewpoint_decimal: Optional[Number] = None 202 maximum_temperature_24: Optional[Number] = None 203 minimum_temperature_24: Optional[Number] = None 204 precip_hourly: Optional[Number] = None 205 sea_level_pressure: Optional[Number] = None 206 snow_depth: Optional[Number] = None 207 temperature_decimal: Optional[Number] = None
RemarksData( maximum_temperature_6: Optional[Number] = None, minimum_temperature_6: Optional[Number] = None, pressure_tendency: Optional[PressureTendency] = None, precip_36_hours: Optional[Number] = None, precip_24_hours: Optional[Number] = None, sunshine_minutes: Optional[Number] = None, codes: List[Code] = <factory>, dewpoint_decimal: Optional[Number] = None, maximum_temperature_24: Optional[Number] = None, minimum_temperature_24: Optional[Number] = None, precip_hourly: Optional[Number] = None, sea_level_pressure: Optional[Number] = None, snow_depth: Optional[Number] = None, temperature_decimal: Optional[Number] = None)
codes: List[Code]
@dataclass
class
ReportData:
210@dataclass 211class ReportData: 212 raw: str 213 sanitized: str 214 station: Optional[str] 215 time: Optional[Timestamp] 216 remarks: Optional[str]
ReportData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str])
time: Optional[Timestamp]
232@dataclass 233class MetarData(ReportData, SharedData): 234 dewpoint: Optional[Number] 235 relative_humidity: Optional[float] 236 remarks_info: Optional[RemarksData] 237 runway_visibility: List[RunwayVisibility] 238 temperature: Optional[Number] 239 wind_variable_direction: List[Number] 240 density_altitude: Optional[int] = None 241 pressure_altitude: Optional[int] = None
MetarData( altimeter: Optional[Number], clouds: List[Cloud], flight_rules: str, other: List[str], visibility: Optional[Number], wind_direction: Optional[Number], wind_gust: Optional[Number], wind_speed: Optional[Number], wx_codes: List[Code], raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], dewpoint: Optional[Number], relative_humidity: Optional[float], remarks_info: Optional[RemarksData], runway_visibility: List[RunwayVisibility], temperature: Optional[Number], wind_variable_direction: List[Number], density_altitude: Optional[int] = None, pressure_altitude: Optional[int] = None)
dewpoint: Optional[Number]
remarks_info: Optional[RemarksData]
runway_visibility: List[RunwayVisibility]
temperature: Optional[Number]
wind_variable_direction: List[Number]
244@dataclass 245class TafLineData(SharedData): 246 end_time: Optional[Timestamp] 247 icing: List[str] 248 probability: Optional[Number] 249 raw: str 250 sanitized: str 251 start_time: Optional[Timestamp] 252 transition_start: Optional[Timestamp] 253 turbulence: List[str] 254 type: str 255 wind_shear: Optional[str] 256 wind_variable_direction: Optional[List[Number]]
TafLineData( altimeter: Optional[Number], clouds: List[Cloud], flight_rules: str, other: List[str], visibility: Optional[Number], wind_direction: Optional[Number], wind_gust: Optional[Number], wind_speed: Optional[Number], wx_codes: List[Code], end_time: Optional[Timestamp], icing: List[str], probability: Optional[Number], raw: str, sanitized: str, start_time: Optional[Timestamp], transition_start: Optional[Timestamp], turbulence: List[str], type: str, wind_shear: Optional[str], wind_variable_direction: Optional[List[Number]])
end_time: Optional[Timestamp]
probability: Optional[Number]
start_time: Optional[Timestamp]
transition_start: Optional[Timestamp]
wind_variable_direction: Optional[List[Number]]
Inherited Members
259@dataclass 260class TafData(ReportData): 261 forecast: List[TafLineData] 262 start_time: Optional[Timestamp] 263 end_time: Optional[Timestamp] 264 max_temp: Optional[str] = None 265 min_temp: Optional[str] = None 266 alts: Optional[List[str]] = None 267 temps: Optional[List[str]] = None 268 remarks_info: Optional[RemarksData] = None
TafData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[TafLineData], start_time: Optional[Timestamp], end_time: Optional[Timestamp], max_temp: Optional[str] = None, min_temp: Optional[str] = None, alts: Optional[List[str]] = None, temps: Optional[List[str]] = None, remarks_info: Optional[RemarksData] = None)
forecast: List[TafLineData]
start_time: Optional[Timestamp]
end_time: Optional[Timestamp]
@dataclass
class
ReportTrans:
279@dataclass 280class MetarTrans(ReportTrans): 281 dewpoint: str 282 remarks: dict 283 temperature: str 284 wind: str
MetarTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, dewpoint: str, remarks: dict, temperature: str, wind: str)
Inherited Members
287@dataclass 288class TafLineTrans(ReportTrans): 289 icing: str 290 turbulence: str 291 wind: str 292 wind_shear: str
TafLineTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, icing: str, turbulence: str, wind: str, wind_shear: str)
Inherited Members
@dataclass
class
TafTrans:
295@dataclass 296class TafTrans: 297 forecast: List[TafLineTrans] 298 max_temp: str 299 min_temp: str 300 remarks: dict
TafTrans( forecast: List[TafLineTrans], max_temp: str, min_temp: str, remarks: dict)
forecast: List[TafLineTrans]
@dataclass
class
Turbulence:
303@dataclass 304class Turbulence: 305 severity: str 306 floor: Optional[Number] = None 307 ceiling: Optional[Number] = None
Icing( severity: str, floor: Optional[Number] = None, ceiling: Optional[Number] = None, type: Optional[str] = None)
Inherited Members
315@dataclass 316class PirepData(ReportData): 317 # pylint: disable=invalid-name 318 aircraft: Union[Aircraft, str, None] = None 319 altitude: Union[Number, str, None] = None 320 clouds: Optional[List[Cloud]] = None 321 flight_visibility: Optional[Number] = None 322 icing: Optional[Icing] = None 323 location: Optional[Location] = None 324 other: Optional[List[str]] = None 325 temperature: Optional[Number] = None 326 turbulence: Optional[Turbulence] = None 327 type: Optional[str] = None 328 wx_codes: Optional[List[Code]] = None
PirepData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], aircraft: Union[Aircraft, str, NoneType] = None, altitude: Union[Number, str, NoneType] = None, clouds: Optional[List[Cloud]] = None, flight_visibility: Optional[Number] = None, icing: Optional[Icing] = None, location: Optional[Location] = None, other: Optional[List[str]] = None, temperature: Optional[Number] = None, turbulence: Optional[Turbulence] = None, type: Optional[str] = None, wx_codes: Optional[List[Code]] = None)
AirepData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str])
@dataclass
class
Bulletin:
Bulletin(repr: str, type: Code, country: str, number: int)
type: Code
@dataclass
class
Movement:
344@dataclass 345class Movement: 346 repr: str 347 direction: Optional[Number] 348 speed: Optional[Number]
direction: Optional[Number]
speed: Optional[Number]
@dataclass
class
AirSigObservation:
351@dataclass 352class AirSigObservation: 353 type: Optional[Code] 354 start_time: Optional[Timestamp] 355 end_time: Optional[Timestamp] 356 position: Optional[Coord] 357 floor: Optional[Number] 358 ceiling: Optional[Number] 359 coords: List[Coord] 360 bounds: List[str] 361 movement: Optional[Movement] 362 intensity: Optional[Code] 363 other: List[str] 364 365 @property 366 def poly(self) -> Optional[Polygon]: 367 if Polygon is None: 368 raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature") 369 return Polygon([c.pair for c in self.coords]) if len(self.coords) > 2 else None
AirSigObservation( type: Optional[Code], start_time: Optional[Timestamp], end_time: Optional[Timestamp], position: Optional[Coord], floor: Optional[Number], ceiling: Optional[Number], coords: List[Coord], bounds: List[str], movement: Optional[Movement], intensity: Optional[Code], other: List[str])
type: Optional[Code]
start_time: Optional[Timestamp]
end_time: Optional[Timestamp]
position: Optional[Coord]
floor: Optional[Number]
ceiling: Optional[Number]
coords: List[Coord]
movement: Optional[Movement]
intensity: Optional[Code]
372@dataclass 373class AirSigmetData(ReportData): 374 bulletin: Bulletin 375 issuer: str 376 correction: Optional[str] 377 area: str 378 type: str 379 start_time: Optional[Timestamp] 380 end_time: Optional[Timestamp] 381 body: str 382 region: str 383 observation: Optional[AirSigObservation] 384 forecast: Optional[AirSigObservation]
AirSigmetData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], bulletin: Bulletin, issuer: str, correction: Optional[str], area: str, type: str, start_time: Optional[Timestamp], end_time: Optional[Timestamp], body: str, region: str, observation: Optional[AirSigObservation], forecast: Optional[AirSigObservation])
bulletin: Bulletin
start_time: Optional[Timestamp]
end_time: Optional[Timestamp]
observation: Optional[AirSigObservation]
forecast: Optional[AirSigObservation]
@dataclass
class
Qualifiers:
387@dataclass 388class Qualifiers: 389 repr: str 390 fir: str 391 subject: Optional[Code] 392 condition: Optional[Code] 393 traffic: Optional[Code] 394 purpose: List[Code] 395 scope: List[Code] 396 lower: Optional[Number] 397 upper: Optional[Number] 398 coord: Optional[Coord] 399 radius: Optional[Number]
Qualifiers( repr: str, fir: str, subject: Optional[Code], condition: Optional[Code], traffic: Optional[Code], purpose: List[Code], scope: List[Code], lower: Optional[Number], upper: Optional[Number], coord: Optional[Coord], radius: Optional[Number])
subject: Optional[Code]
condition: Optional[Code]
traffic: Optional[Code]
purpose: List[Code]
scope: List[Code]
lower: Optional[Number]
upper: Optional[Number]
coord: Optional[Coord]
radius: Optional[Number]
402@dataclass 403class NotamData(ReportData): 404 number: Optional[str] 405 replaces: Optional[str] 406 type: Optional[Code] 407 qualifiers: Optional[Qualifiers] 408 start_time: Union[Timestamp, Code, None] 409 end_time: Union[Timestamp, Code, None] 410 schedule: Optional[str] 411 body: str 412 lower: Optional[Number] 413 upper: Optional[Number]
NotamData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], number: Optional[str], replaces: Optional[str], type: Optional[Code], qualifiers: Optional[Qualifiers], start_time: Union[Timestamp, Code, NoneType], end_time: Union[Timestamp, Code, NoneType], schedule: Optional[str], body: str, lower: Optional[Number], upper: Optional[Number])
type: Optional[Code]
qualifiers: Optional[Qualifiers]
lower: Optional[Number]
upper: Optional[Number]
@dataclass
class
GfsPeriod:
416@dataclass 417class GfsPeriod: 418 time: Timestamp 419 temperature: Number 420 dewpoint: Number 421 cloud: Code 422 temperature_minmax: Optional[Number] = None 423 precip_chance_12: Optional[Number] = None 424 precip_amount_12: Optional[Code] = None 425 thunderstorm_12: Optional[Number] = None 426 severe_storm_12: Optional[Number] = None 427 freezing_precip: Optional[Number] = None 428 precip_type: Optional[Code] = None 429 snow: Optional[Number] = None
GfsPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Code] = None, thunderstorm_12: Optional[Number] = None, severe_storm_12: Optional[Number] = None, freezing_precip: Optional[Number] = None, precip_type: Optional[Code] = None, snow: Optional[Number] = None)
time: Timestamp
temperature: Number
dewpoint: Number
cloud: Code
432@dataclass 433class MavPeriod(GfsPeriod): 434 wind_direction: Optional[Number] = None 435 wind_speed: Optional[Number] = None 436 precip_chance_6: Optional[Number] = None 437 precip_amount_6: Optional[Code] = None 438 thunderstorm_6: Optional[Number] = None 439 severe_storm_6: Optional[Number] = None 440 ceiling: Optional[Code] = None 441 visibility: Optional[Code] = None 442 vis_obstruction: Optional[Code] = None
MavPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Code] = None, thunderstorm_12: Optional[Number] = None, severe_storm_12: Optional[Number] = None, freezing_precip: Optional[Number] = None, precip_type: Optional[Code] = None, snow: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, precip_chance_6: Optional[Number] = None, precip_amount_6: Optional[Code] = None, thunderstorm_6: Optional[Number] = None, severe_storm_6: Optional[Number] = None, ceiling: Optional[Code] = None, visibility: Optional[Code] = None, vis_obstruction: Optional[Code] = None)
445@dataclass 446class MexPeriod(GfsPeriod): 447 precip_chance_24: Optional[Number] = None 448 precip_amount_24: Optional[Code] = None 449 thunderstorm_24: Optional[Number] = None 450 severe_storm_24: Optional[Number] = None 451 rain_snow_mix: Optional[Number] = None 452 snow_amount_24: Optional[Code] = None
MexPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Code] = None, thunderstorm_12: Optional[Number] = None, severe_storm_12: Optional[Number] = None, freezing_precip: Optional[Number] = None, precip_type: Optional[Code] = None, snow: Optional[Number] = None, precip_chance_24: Optional[Number] = None, precip_amount_24: Optional[Code] = None, thunderstorm_24: Optional[Number] = None, severe_storm_24: Optional[Number] = None, rain_snow_mix: Optional[Number] = None, snow_amount_24: Optional[Code] = None)
MavData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[MavPeriod])
forecast: List[MavPeriod]
MexData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[MexPeriod])
forecast: List[MexPeriod]
465@dataclass 466class NbmUnits(Units): 467 duration: str 468 solar_radiation: str 469 wave_height: str
NbmUnits( accumulation: str, altimeter: str, altitude: str, temperature: str, visibility: str, wind_speed: str, duration: str, solar_radiation: str, wave_height: str)
Inherited Members
@dataclass
class
NbmPeriod:
472@dataclass 473class NbmPeriod: 474 time: Timestamp 475 temperature: Optional[Number] = None 476 dewpoint: Optional[Number] = None 477 sky_cover: Optional[Number] = None 478 wind_direction: Optional[Number] = None 479 wind_speed: Optional[Number] = None 480 wind_gust: Optional[Number] = None 481 snow_level: Optional[Number] = None 482 precip_duration: Optional[Number] = None 483 freezing_precip: Optional[Number] = None 484 snow: Optional[Number] = None 485 sleet: Optional[Number] = None 486 rain: Optional[Number] = None 487 solar_radiation: Optional[Number] = None 488 wave_height: Optional[Number] = None
NbmPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None)
time: Timestamp
502@dataclass 503class NbhPeriod(NbhsShared): 504 precip_chance_1: Optional[Number] = None 505 precip_chance_6: Optional[Number] = None 506 precip_amount_1: Optional[Number] = None 507 thunderstorm_1: Optional[Number] = None 508 snow_amount_1: Optional[Number] = None 509 icing_amount_1: Optional[Number] = None
NbhPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, ceiling: Optional[Number] = None, visibility: Optional[Number] = None, cloud_base: Optional[Number] = None, mixing_height: Optional[Number] = None, transport_wind_direction: Optional[Number] = None, transport_wind_speed: Optional[Number] = None, haines: Optional[List[Number]] = None, precip_chance_1: Optional[Number] = None, precip_chance_6: Optional[Number] = None, precip_amount_1: Optional[Number] = None, thunderstorm_1: Optional[Number] = None, snow_amount_1: Optional[Number] = None, icing_amount_1: Optional[Number] = None)
512@dataclass 513class NbsPeriod(NbhsShared): 514 temperature_minmax: Optional[Number] = None 515 precip_chance_6: Optional[Number] = None 516 precip_chance_12: Optional[Number] = None 517 precip_amount_6: Optional[Number] = None 518 precip_amount_12: Optional[Number] = None 519 precip_duration: Optional[Number] = None 520 thunderstorm_3: Optional[Number] = None 521 thunderstorm_6: Optional[Number] = None 522 thunderstorm_12: Optional[Number] = None 523 snow_amount_6: Optional[Number] = None 524 icing_amount_6: Optional[Number] = None
NbsPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, ceiling: Optional[Number] = None, visibility: Optional[Number] = None, cloud_base: Optional[Number] = None, mixing_height: Optional[Number] = None, transport_wind_direction: Optional[Number] = None, transport_wind_speed: Optional[Number] = None, haines: Optional[List[Number]] = None, temperature_minmax: Optional[Number] = None, precip_chance_6: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_6: Optional[Number] = None, precip_amount_12: Optional[Number] = None, thunderstorm_3: Optional[Number] = None, thunderstorm_6: Optional[Number] = None, thunderstorm_12: Optional[Number] = None, snow_amount_6: Optional[Number] = None, icing_amount_6: Optional[Number] = None)
527@dataclass 528class NbePeriod(NbmPeriod): 529 temperature_minmax: Optional[Number] = None 530 precip_chance_12: Optional[Number] = None 531 precip_amount_12: Optional[Number] = None 532 precip_amount_24: Optional[Number] = None 533 thunderstorm_12: Optional[Number] = None 534 snow_amount_12: Optional[Number] = None 535 snow_amount_24: Optional[Number] = None 536 icing_amount_12: Optional[Number] = None
NbePeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Number] = None, precip_amount_24: Optional[Number] = None, thunderstorm_12: Optional[Number] = None, snow_amount_12: Optional[Number] = None, snow_amount_24: Optional[Number] = None, icing_amount_12: Optional[Number] = None)
539@dataclass 540class NbxPeriod(NbmPeriod): 541 precip_chance_12: Optional[Number] = None 542 precip_amount_12: Optional[Number] = None 543 precip_amount_24: Optional[Number] = None 544 snow_amount_12: Optional[Number] = None 545 icing_amount_12: Optional[Number] = None
NbxPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Number] = None, precip_amount_24: Optional[Number] = None, snow_amount_12: Optional[Number] = None, icing_amount_12: Optional[Number] = None)
NbhData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbhPeriod])
forecast: List[NbhPeriod]
NbsData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbsPeriod])
forecast: List[NbsPeriod]
NbeData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbePeriod])
forecast: List[NbePeriod]
NbxData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbxPeriod])
forecast: List[NbxPeriod]
@dataclass
class
Sanitization:
605@dataclass 606class Sanitization: 607 removed: List[str] = field(default_factory=list) 608 replaced: Dict[str, str] = field(default_factory=dict) 609 duplicates_found: bool = False 610 extra_spaces_found: bool = False 611 extra_spaces_needed: bool = False 612 613 @property 614 def errors_found(self) -> bool: 615 return bool( 616 self.removed 617 or self.replaced 618 or self.duplicates_found 619 or self.extra_spaces_found 620 or self.extra_spaces_needed 621 ) 622 623 def log(self, item: str, replacement: Optional[str] = None) -> None: 624 """Log a changed item. Calling without a replacement assumes removal""" 625 item = item.strip() 626 if not item: 627 return 628 if replacement is None: 629 self.removed.insert(0, item) 630 return 631 replacement = replacement.strip() 632 if not replacement: 633 self.removed.insert(0, item) 634 elif item != replacement: 635 self.replaced[item] = replacement 636 637 def log_list(self, before: List[str], after: List[str]) -> None: 638 """Log list differences. Assumes that list length and order haven't changed""" 639 for item, replacement in zip(before, after): 640 if item != replacement: 641 self.log(item, replacement)
Sanitization( removed: List[str] = <factory>, replaced: Dict[str, str] = <factory>, duplicates_found: bool = False, extra_spaces_found: bool = False, extra_spaces_needed: bool = False)
def
log(self, item: str, replacement: Optional[str] = None) -> None:
623 def log(self, item: str, replacement: Optional[str] = None) -> None: 624 """Log a changed item. Calling without a replacement assumes removal""" 625 item = item.strip() 626 if not item: 627 return 628 if replacement is None: 629 self.removed.insert(0, item) 630 return 631 replacement = replacement.strip() 632 if not replacement: 633 self.removed.insert(0, item) 634 elif item != replacement: 635 self.replaced[item] = replacement
Log a changed item. Calling without a replacement assumes removal
def
log_list(self, before: List[str], after: List[str]) -> None:
637 def log_list(self, before: List[str], after: List[str]) -> None: 638 """Log list differences. Assumes that list length and order haven't changed""" 639 for item, replacement in zip(before, after): 640 if item != replacement: 641 self.log(item, replacement)
Log list differences. Assumes that list length and order haven't changed