Skip to content

Commit 4ccf71c

Browse files
authored
Merge pull request #36 from WeatherGod/strict_mode
ENH: optional suppression of ParserError for undecoded non-remark ele…
2 parents 22f0dd7 + ce290c2 commit 4ccf71c

File tree

2 files changed

+71
-8
lines changed

2 files changed

+71
-8
lines changed

metar/Metar.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949

5050
import re
5151
import datetime
52+
import warnings
53+
5254
from metar.Datatypes import *
5355

5456
## Exceptions
@@ -305,8 +307,23 @@ def _unparsedGroup( self, d ):
305307
class Metar(object):
306308
"""METAR (aviation meteorology report)"""
307309

308-
def __init__( self, metarcode, month=None, year=None, utcdelta=None):
309-
"""Parse raw METAR code."""
310+
def __init__(self, metarcode, month=None, year=None, utcdelta=None,
311+
strict=True):
312+
"""
313+
Parse raw METAR code.
314+
315+
Can also provide a *month* and/or *year* for unambigous date
316+
determination. If not provided, then the month and year are
317+
guessed from the current date. The *utcdelta* (in hours) is
318+
reserved for future use for handling time-zones.
319+
320+
By default, the constructor will raise a `ParserError` if there are
321+
non-remark elements that were left undecoded. However, one can pass
322+
*strict=False* keyword argument to suppress raising this
323+
exception. One can then detect if decoding is complete by checking
324+
the :attr:`decode_completed` attribute.
325+
326+
"""
310327
self.code = metarcode # original METAR code
311328
self.type = 'METAR' # METAR (routine) or SPECI (special)
312329
self.mod = "AUTO" # AUTO (automatic) or COR (corrected)
@@ -405,11 +422,23 @@ def __init__( self, metarcode, month=None, year=None, utcdelta=None):
405422
break
406423

407424
except Exception as err:
408-
raise ParserError(handler.__name__+" failed while processing '"+code+"'\n"+" ".join(err.args))
409-
raise err
425+
raise ParserError(handler.__name__+" failed while processing '"+
426+
code+"'\n"+" ".join(err.args))
427+
410428
if self._unparsed_groups:
411429
code = ' '.join(self._unparsed_groups)
412-
raise ParserError("Unparsed groups in body '"+code+"' while processing '"+metarcode+"'")
430+
message = "Unparsed groups in body '%s' while processing '%s'" % (code, metarcode)
431+
if strict:
432+
raise ParserError(message)
433+
else:
434+
warnings.warn(message, RuntimeWarning)
435+
436+
@property
437+
def decode_completed(self):
438+
"""
439+
Indicate whether the decoding was complete for non-remark elements.
440+
"""
441+
return not self._unparsed_groups
413442

414443
def _do_trend_handlers(self, code):
415444
for pattern, handler, repeatable in self.trend_handlers:

test/test_metar.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import unittest
2+
import warnings
3+
24
from metar import Metar
35

46
# METAR fragments used in tests, below
@@ -12,9 +14,6 @@
1214

1315
class MetarTest(unittest.TestCase):
1416

15-
def raisesParserError(self, code):
16-
self.assertRaises(Metar.ParserError, Metar.Metar, code )
17-
1817
def raisesParserError(self, code):
1918
self.assertRaises(Metar.ParserError, Metar.Metar, code )
2019

@@ -46,6 +45,7 @@ def test_021_parseStation_illegal(self):
4645
def test_030_parseTime_legal(self):
4746
"""Check parsing of the time stamp."""
4847
report = Metar.Metar("KEWR 101651Z")
48+
assert report.decode_completed
4949
self.assertEqual( report.time.day, 10 )
5050
self.assertEqual( report.time.hour, 16 )
5151
self.assertEqual( report.time.minute, 51 )
@@ -59,6 +59,7 @@ def test_031_parseTime_specify_year(self):
5959
other_year = 2003
6060

6161
report = Metar.Metar("KEWR 101651Z", year=other_year)
62+
assert report.decode_completed
6263
self.assertEqual( report.time.year, other_year )
6364

6465
def test_032_parseTime_specify_month(self):
@@ -67,6 +68,7 @@ def test_032_parseTime_specify_month(self):
6768
last_year = today.year - 1
6869

6970
report = Metar.Metar("KEWR 101651Z", month=last_month)
71+
assert report.decode_completed
7072
self.assertEqual( report.time.month, last_month )
7173

7274
def test_033_parseTime_auto_month(self):
@@ -78,6 +80,7 @@ def test_033_parseTime_auto_month(self):
7880

7981
timestr = "%02d1651Z" % (next_day)
8082
report = Metar.Metar("KEWR " + timestr)
83+
assert report.decode_completed
8184
self.assertEqual( report.time.day, next_day )
8285
self.assertEqual( report.time.month, last_month )
8386
if today.month > 1:
@@ -91,6 +94,7 @@ def test_034_parseTime_auto_year(self):
9194
last_year = today.year - 1
9295

9396
report = Metar.Metar("KEWR 101651Z", month=next_month)
97+
assert report.decode_completed
9498
self.assertEqual( report.time.month, next_month )
9599
if next_month > 1:
96100
self.assertEqual( report.time.year, last_year )
@@ -106,6 +110,7 @@ def test_035_parseTime_suppress_auto_month(self):
106110

107111
timestr = "%02d1651Z" % (next_day)
108112
report = Metar.Metar("KEWR " + timestr, month=1)
113+
assert report.decode_completed
109114
self.assertEqual( report.time.day, next_day )
110115
self.assertEqual( report.time.month, 1 )
111116
if today.month > 1:
@@ -152,6 +157,7 @@ def test_043_parseModifier_illegal(self):
152157
def test_140_parseWind(self):
153158
"""Check parsing of wind groups."""
154159
report = Metar.Metar(sta_time+"09010KT" )
160+
assert report.decode_completed
155161
self.assertEqual( report.wind_dir.value(), 90 )
156162
self.assertEqual( report.wind_speed.value(), 10 )
157163
self.assertEqual( report.wind_gust, None )
@@ -160,35 +166,42 @@ def test_140_parseWind(self):
160166
self.assertEqual( report.wind(), "E at 10 knots" )
161167

162168
report = Metar.Metar(sta_time+"09010MPS" )
169+
assert report.decode_completed
163170
self.assertEqual( report.wind_speed.value(), 10 )
164171
self.assertEqual( report.wind_speed.value("KMH"), 36 )
165172
self.assertEqual( report.wind(), "E at 19 knots" )
166173
self.assertEqual( report.wind("MPS"), "E at 10 mps" )
167174
self.assertEqual( report.wind("KMH"), "E at 36 km/h" )
168175

169176
report = Metar.Metar(sta_time+"09010KMH" )
177+
assert report.decode_completed
170178
self.assertEqual( report.wind_speed.value(), 10 )
171179
self.assertEqual( report.wind(), "E at 5 knots" )
172180
self.assertEqual( report.wind('KMH'), "E at 10 km/h" )
173181

174182
report = Metar.Metar(sta_time+"090010KT" )
183+
assert report.decode_completed
175184
self.assertEqual( report.wind_dir.value(), 90 )
176185
self.assertEqual( report.wind_speed.value(), 10 )
177186

178187
report = Metar.Metar(sta_time+"000000KT" )
188+
assert report.decode_completed
179189
self.assertEqual( report.wind_dir.value(), 0 )
180190
self.assertEqual( report.wind_speed.value(), 0 )
181191
self.assertEqual( report.wind(), "calm" )
182192

183193
report = Metar.Metar(sta_time+"VRB03KT" )
194+
assert report.decode_completed
184195
self.assertEqual( report.wind_dir, None )
185196
self.assertEqual( report.wind_speed.value(), 3 )
186197
self.assertEqual( report.wind(), "variable at 3 knots" )
187198

188199
report = Metar.Metar(sta_time+"VRB00KT" )
200+
assert report.decode_completed
189201
self.assertEqual( report.wind(), "calm" )
190202

191203
report = Metar.Metar(sta_time+"VRB03G40KT" )
204+
assert report.decode_completed
192205
self.assertEqual( report.wind_dir, None )
193206
self.assertEqual( report.wind_speed.value(), 3 )
194207
self.assertEqual( report.wind_gust.value(), 40 )
@@ -197,6 +210,7 @@ def test_140_parseWind(self):
197210
self.assertEqual( report.wind(), "variable at 3 knots, gusting to 40 knots" )
198211

199212
report = Metar.Metar(sta_time+"21010G30KT" )
213+
assert report.decode_completed
200214
self.assertEqual( report.wind(), "SSW at 10 knots, gusting to 30 knots" )
201215

202216
report = Metar.Metar(sta_time+"21010KT 180V240" )
@@ -440,6 +454,26 @@ def report(sky_conditions):
440454
self.assertEqual( report('CLR').sky_conditions(), 'clear' )
441455
self.assertEqual( report('NSC').sky_conditions(), 'clear' )
442456

457+
def test_not_strict_mode(self):
458+
# This example metar has an extraneous 'M' in it, but the rest is fine
459+
# Let's make sure that we can activate a non-strict mode, and flag that there
460+
# are unparsed portions
461+
code = 'K9L2 100958Z AUTO 33006KT 10SM CLR M A3007 RMK AO2 SLPNO FZRANO $'
462+
self.raisesParserError(code)
463+
464+
with warnings.catch_warnings(record=True) as w:
465+
report = Metar.Metar(code, strict=False)
466+
assert len(w) == 1
467+
468+
assert not report.decode_completed
469+
self.assertEqual( report.cycle, 10 )
470+
self.assertEqual( report.mod, 'AUTO' )
471+
self.assertEqual( report.recent, [] )
472+
self.assertEqual( report.station_id, 'K9L2' )
473+
self.assertEqual( report.vis.value(), 10 )
474+
self.assertEqual( report.sky_conditions(), 'clear' )
475+
476+
443477
if __name__=='__main__':
444478
unittest.main( )
445479

0 commit comments

Comments
 (0)