Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pymetar for openSUSE:Factory checked in at 2024-01-08 23:44:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pymetar (Old) and /work/SRC/openSUSE:Factory/.python-pymetar.new.21961 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pymetar" Mon Jan 8 23:44:41 2024 rev:10 rq:1137448 version:1.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pymetar/python-pymetar.changes 2020-05-26 17:19:06.147946702 +0200 +++ /work/SRC/openSUSE:Factory/.python-pymetar.new.21961/python-pymetar.changes 2024-01-08 23:44:54.753549030 +0100 @@ -1,0 +2,6 @@ +Sun Jan 7 21:08:41 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 1.4 + * no changelog available + +------------------------------------------------------------------- Old: ---- pymetar-1.1.tar.gz New: ---- pymetar-1.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pymetar.spec ++++++ --- /var/tmp/diff_new_pack.xALcSu/_old 2024-01-08 23:44:55.521576954 +0100 +++ /var/tmp/diff_new_pack.xALcSu/_new 2024-01-08 23:44:55.525577099 +0100 @@ -1,7 +1,7 @@ # -# spec file for package python-pymetar +# spec file # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2024 SUSE LLC # Copyright (c) 2012 Malcolm J Lewis <malcolmle...@opensuse.org> # # All modifications and additions to the file contributed by third parties @@ -18,23 +18,22 @@ %define modname pymetar -%define oldpython python -%{?!python_module:%define python_module() python-%{**} python3-%{**}} -%define skip_python2 1 +%{?sle15_python_module_pythons} Name: python-%{modname} -Version: 1.1 +Version: 1.4 Release: 0 Summary: METAR weather report parser License: GPL-2.0-or-later Group: Development/Languages/Python URL: https://www.schwarzvogel.de/software-pymetar.shtml -Source0: http://www.schwarzvogel.de/pkgs/pymetar-%{version}.tar.gz +Source0: https://www.schwarzvogel.de/pkgs/pymetar-%{version}.tar.gz +BuildRequires: %{python_module pip} BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires(post): update-alternatives -Requires(postun): update-alternatives -Obsoletes: %{oldpython}-%{modname} +Requires(postun):update-alternatives BuildArch: noarch %python_subpackages @@ -46,14 +45,11 @@ %setup -q -n %{modname}-%{version} %build -%python_build +%pyproject_wheel %install -%python_install -%python_clone -a %{buildroot}%{_mandir}/man1/%{modname}.1 +%pyproject_install %python_clone -a %{buildroot}%{_bindir}/%{modname} -# we install docs on our own -rm -r %{buildroot}%{_datadir}/doc/ %python_expand %fdupes %{buildroot}%{$python_sitelib} %check @@ -64,15 +60,16 @@ cd - %post -%python_install_alternative %{modname} %{modname}.1 +%python_install_alternative %{modname} %postun -%python_uninstall_alternative %{modname} %{modname}.1 +%python_uninstall_alternative %{modname} %files %{python_files} %doc README.md %license COPYING %python_alternative %{_bindir}/%{modname} -%{python_sitelib}/* -%python_alternative %{_mandir}/man1/%{modname}.1%{?ext_man} +%{python_sitelib}/pymetar.py +%pycache_only %{python_sitelib}/__pycache__/pymetar* +%{python_sitelib}/pymetar-%{version}.dist-info ++++++ pymetar-1.1.tar.gz -> pymetar-1.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/PKG-INFO new/pymetar-1.4/PKG-INFO --- old/pymetar-1.1/PKG-INFO 2018-12-25 18:55:35.000000000 +0100 +++ new/pymetar-1.4/PKG-INFO 2021-10-15 10:29:53.842224600 +0200 @@ -1,98 +1,97 @@ Metadata-Version: 2.1 Name: pymetar -Version: 1.1 -Summary: -PyMETAR is a python module and command line tool designed to fetch Metar -reports from the NOAA (https://www.noaa.gov) and allow access to the -included weather information. - +Version: 1.4 +Summary: Pymetar is a python module and command line tool that fetches and parses METAR reports Home-page: http://www.schwarzvogel.de/software-pymetar.shtml Author: Tobias Klausmann Author-email: klausman-pyme...@schwarzvogel.de License: UNKNOWN -Description: # PyMETAR - a module to fetch and parse METAR reports - - **NOTE:** If you're looking for information regarding Python 2 and PyMETAR, see - the end of this document. - - The National Oceanic and Atmospheric Administration (NOAA, http://www.noaa.gov/) - provides easy access to the weather reports generated by a large number of - weather stations (mostly at airports) worldwide. Those reports are called METAR - reports and are delivered as plain text files that look like this: - - ``` - Duesseldorf, Germany (EDDL) 51-18N 006-46E 41M - Jul 26, 2002 - 03:50 AM EST / 2002.07.26 0850 UTC - Wind: from the SW (220 degrees) at 9 MPH (8 KT):0 - Visibility: 3 mile(s):0 - Sky conditions: mostly cloudy - Weather: mist - Temperature: 60 F (16 C) - Dew Point: 57 F (14 C) - Relative Humidity: 87% - Pressure (altimeter): 30.00 in. Hg (1016 hPa) - ob: EDDL 260850Z 22008KT 5000 BR SCT006 BKN012 16/14 Q1016 BECMG BKN015 - cycle: 9 - ``` - - While this is convenient if you just want to quickly look up the data, there's - some effort involved in parsing all of this into a format that is digestible by - a program. Plus, you have to remember the base URL of the reports and fetch the - file. - - This is what this library does. All you have to do is find the station you're - interested in at http://www.aviationweather.gov/metar and feed the 4-letter - station code to the MetarReport class. - - On the user end, the library provides a large number of methods to fetch the - parsed information in a plethora of formats. Those functions are described in - the file `librarydoc.txt` which was in turn generated using PyDoc. - - PyMETAR uses `urllib2` (and its successors), which in turn makes it easy to honor - the environment variable `http_proxy`. This simplifies use of a proxy - tremendously. Thanks go to Davide Di Blasi for both suggesting and implementing - this. The environment variable is easy to use: just set it to: - - ``` - http://username:passw...@proxy.yourdomain.com:port - ``` - - You can also specify a proxy (with the same syntax) as an argument to the - fetching function. This is sometimes easier when using PyMETAR in a web - application environment (such as ZopeWeatherApplet by Jerome Alet). See - `librarydoc.txt` for details on how to accomplish that. - - You can also use IPs instead of hostnames, of course. When in doubt, ask your - proxy admin. - - Due to some peculiarities in the METAR format, I can not rule out the - possibility that the library barfs on some less common types of reports. If you - encounter such a report, please save it and the error messages you get as - completely as possible and send them to me at `klausman-pyme...@schwarzvogel.de` - --- Thanks a lot! - - Of course you may send all the other bugs you encounter to me, too. As this is a - Python library, chances are that you are Python programmer and can provide a - patch. If you do so, please, by all means use spaces for indentation, four per - level, that makes merging the patch a lot easier. - - ## Python 2, 3, PyPy, PyPy3 and so on - - This version of PyMETAR supports Python 3.x and PyPy3 only. The last version to - have Python2 as its main target was v0.21. - - Compatibility with PyPy (2.x), Jython and other interpreters not mentioned - above is unknown. - - I maintain a branch (called Python2) on git which is the state of Pymetar as it - was for the last version supporting Python 2. That branch is in maintenance - mode, i.e. it will only receive fixes for security issues or bugs that can be - fixed without too much fuss. That branch will go away in 2020. - - [0] http://python-future.org/ - Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) Classifier: Operating System :: OS Independent Description-Content-Type: text/markdown +License-File: COPYING + +# PyMETAR - a module to fetch and parse METAR reports + +**NOTE:** If you're looking for information regarding Python 2 and PyMETAR, see +the end of this document. + +The National Oceanic and Atmospheric Administration (NOAA, http://www.noaa.gov/) +provides easy access to the weather reports generated by a large number of +weather stations (mostly at airports) worldwide. Those reports are called METAR +reports and are delivered as plain text files that look like this: + +``` +Duesseldorf, Germany (EDDL) 51-18N 006-46E 41M +Jul 26, 2002 - 03:50 AM EST / 2002.07.26 0850 UTC +Wind: from the SW (220 degrees) at 9 MPH (8 KT):0 +Visibility: 3 mile(s):0 +Sky conditions: mostly cloudy +Weather: mist +Temperature: 60 F (16 C) +Dew Point: 57 F (14 C) +Relative Humidity: 87% +Pressure (altimeter): 30.00 in. Hg (1016 hPa) +ob: EDDL 260850Z 22008KT 5000 BR SCT006 BKN012 16/14 Q1016 BECMG BKN015 +cycle: 9 +``` + +While this is convenient if you just want to quickly look up the data, there's +some effort involved in parsing all of this into a format that is digestible by +a program. Plus, you have to remember the base URL of the reports and fetch the +file. + +This is what this library does. All you have to do is find the station you're +interested in at http://www.aviationweather.gov/metar and feed the 4-letter +station code to the MetarReport class. + +On the user end, the library provides a large number of methods to fetch the +parsed information in a plethora of formats. Those functions are described in +the file `librarydoc.txt` which was in turn generated using PyDoc. + +PyMETAR uses `urllib2` (and its successors), which in turn makes it easy to honor +the environment variable `http_proxy`. This simplifies use of a proxy +tremendously. Thanks go to Davide Di Blasi for both suggesting and implementing +this. The environment variable is easy to use: just set it to: + +``` +http://username:passw...@proxy.yourdomain.com:port +``` + +You can also specify a proxy (with the same syntax) as an argument to the +fetching function. This is sometimes easier when using PyMETAR in a web +application environment (such as ZopeWeatherApplet by Jerome Alet). See +`librarydoc.txt` for details on how to accomplish that. + +You can also use IPs instead of hostnames, of course. When in doubt, ask your +proxy admin. + +Due to some peculiarities in the METAR format, I can not rule out the +possibility that the library barfs on some less common types of reports. If you +encounter such a report, please save it and the error messages you get as +completely as possible and send them to me at `klausman-pyme...@schwarzvogel.de` +--- Thanks a lot! + +Of course you may send all the other bugs you encounter to me, too. As this is a +Python library, chances are that you are Python programmer and can provide a +patch. If you do so, please, by all means use spaces for indentation, four per +level, that makes merging the patch a lot easier. + +## Python 2, 3, PyPy, PyPy3 and so on + +This version of PyMETAR supports Python 3.x and PyPy3 only. The last version to +have Python2 as its main target was v0.21. + +Compatibility with PyPy (2.x), Jython and other interpreters not mentioned +above is unknown. + +I maintain a branch (called Python2) on git which is the state of Pymetar as it +was for the last version supporting Python 2. That branch is in maintenance +mode, i.e. it will only receive fixes for security issues or bugs that can be +fixed without too much fuss. That branch will go away in 2020. + +[0] http://python-future.org/ + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/bin/pymetar new/pymetar-1.4/bin/pymetar --- old/pymetar-1.1/bin/pymetar 2018-12-25 18:44:00.000000000 +0100 +++ new/pymetar-1.4/bin/pymetar 2021-10-15 10:28:30.000000000 +0200 @@ -1,7 +1,7 @@ #!/usr/bin/python -tt # -*- coding: iso-8859-15 -*- -__version__ = "1.1" +__version__ = "1.4" import pymetar import sys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/bin/pymetar.py new/pymetar-1.4/bin/pymetar.py --- old/pymetar-1.1/bin/pymetar.py 2018-12-25 18:46:34.000000000 +0100 +++ new/pymetar-1.4/bin/pymetar.py 2021-10-15 10:29:50.000000000 +0200 @@ -20,7 +20,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.""" # -import math import re import urllib.request # noqa: E402 @@ -29,12 +28,12 @@ __author__ = "klausman-pyme...@schwarzvogel.de" -__version__ = "1.1" +__version__ = "1.4" CLOUD_RE_STR = (r"^(CAVOK|CLR|SKC|BKN|SCT|FEW|OVC|NSC)([0-9]{3})?" r"(TCU|CU|CB|SC|CBMAM|ACC|SCSL|CCSL|ACSL)?$") -COND_RE_STR = (r"^(-|\\+)?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|" - r"GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\\+?FC)$") +COND_RE_STR = (r"^[-+]?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|" + r"GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\+?FC)$") class EmptyReportException(Exception): @@ -398,14 +397,13 @@ coords = coords + float(elements[0]) - if compass_dir in ('W', 'S'): + if compass_dir in 'WS': coords = -1.0 * coords return coords class WeatherReport: - """Incorporates both the unparsed textual representation of the weather report and the parsed values as soon as they are filled in by ReportParser.""" @@ -493,8 +491,7 @@ """ Return the wind speed in miles per hour. """ - if self.windspeed is not None: - return self.windspeedmph + return self.windspeedmph def getWindSpeedBeaufort(self): """ @@ -502,7 +499,7 @@ cf. https://en.wikipedia.org/wiki/Beaufort_scale """ if self.windspeed is not None: - return round(math.pow(self.windspeed / 0.8359648, 2 / 3.0)) + return round((self.windspeed / 0.8359648) ** (2 / 3.0)) def getWindSpeedKnots(self): """ @@ -528,8 +525,7 @@ """ Return visibility in km. """ - if self.vis is not None: - return self.vis + return self.vis def getVisibilityMiles(self): """ @@ -688,7 +684,7 @@ Return the time when the observation was made in ISO 8601 format (e.g. 2002-07-25 15:12:00Z) """ - return(metar_to_iso8601(self.rtime)) + return metar_to_iso8601(self.rtime) def getPixmap(self): """ @@ -803,12 +799,10 @@ """ matches = self.match_WeatherPart(COND_RE_STR) for wcond in matches: - if ((len(wcond) > 3) and - (wcond.startswith('+') or wcond.startswith('-'))): - + if len(wcond) > 3 and wcond.startswith(('+', '-')): wcond = wcond[1:] - if wcond.startswith('+') or wcond.startswith('-'): + if wcond.startswith(('+', '-')): pphen = 1 elif len(wcond) < 4: pphen = 0 @@ -835,12 +829,13 @@ strings, only the first one is taken into account! """ matches = [] + # FIXME This mixes direct access to the "code" attribute and the accessor function "getRawMetarCode" which just returns "code". if self.Report.code is not None: myre = re.compile(regexp) for wpart in self.Report.getRawMetarCode().split(): match = myre.match(wpart) if match: - matches.append(match.string[match.start(0): match.end(0)]) + matches.append(match.group()) return matches def ParseReport(self, MetarReport=None): @@ -911,14 +906,13 @@ # The line containing date and time of the report # We have to make sure that the station ID is *not* # in this line to avoid trying to parse the ob: line - elif ((data.find(" UTC")) != -1 and - (data.find(self.Report.givenstationid)) == -1): + elif " UTC" in data and self.Report.givenstationid not in data: rtime = data.split("/")[1] self.Report.rtime = rtime.strip() # temperature - elif (header == "Temperature"): + elif header == "Temperature": fnht, cels = data.split(None, 3)[0:3:2] self.Report.tempf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -926,7 +920,7 @@ # wind chill - elif (header == "Windchill"): + elif header == "Windchill": fnht, cels = data.split(None, 3)[0:3:2] self.Report.w_chillf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -934,14 +928,14 @@ # wind dir and speed - elif (header == "Wind"): - if (data.find("Calm") != -1): + elif header == "Wind": + if "Calm" in data: self.Report.windspeed = 0.0 self.Report.windspeedkt = 0.0 self.Report.windspeedmph = 0.0 self.Report.winddir = None self.Report.windcomp = None - elif (data.find("Variable") != -1): + elif "Variable" in data: speed = data.split(" ", 3)[2] self.Report.windspeed = (float(speed) * 0.44704) self.Report.windspeedkt = int(data.split(" ", 5)[4][1:]) @@ -962,7 +956,7 @@ # visibility - elif (header == "Visibility"): + elif header == "Visibility": for visgroup in data.split(): try: self.Report.vis = float(visgroup) * 1.609344 @@ -973,7 +967,7 @@ # dew point - elif (header == "Dew Point"): + elif header == "Dew Point": fnht, cels = data.split(None, 3)[0:3:2] self.Report.dewpf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -981,36 +975,36 @@ # humidity - elif (header == "Relative Humidity"): + elif header == "Relative Humidity": h = data.split("%", 1)[0] self.Report.humid = int(h) # pressure - elif (header == "Pressure (altimeter)"): + elif header == "Pressure (altimeter)": press = data.split(" ", 1)[0] - self.Report.press = (float(press) * 33.863886) + self.Report.press = float(press) * 33.863886 # 1 in = 25.4 mm => 1 inHg = 25.4 mmHg - self.Report.pressmmHg = (float(press) * 25.4000) + self.Report.pressmmHg = float(press) * 25.4000 # shot weather desc. ("rain", "mist", ...) - elif (header == "Weather"): + elif header == "Weather": self.Report.weather = data # short desc. of sky conditions - elif (header == "Sky conditions"): + elif header == "Sky conditions": self.Report.sky = data # the encoded report itself - elif (header == "ob"): + elif header == "ob": self.Report.code = data.strip() # the cycle value ("time slot") - elif (header == "cycle"): + elif header == "cycle": try: self.Report.cycle = int(data) except ValueError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/pymetar.egg-info/PKG-INFO new/pymetar-1.4/pymetar.egg-info/PKG-INFO --- old/pymetar-1.1/pymetar.egg-info/PKG-INFO 2018-12-25 18:55:35.000000000 +0100 +++ new/pymetar-1.4/pymetar.egg-info/PKG-INFO 2021-10-15 10:29:53.000000000 +0200 @@ -1,98 +1,97 @@ Metadata-Version: 2.1 Name: pymetar -Version: 1.1 -Summary: -PyMETAR is a python module and command line tool designed to fetch Metar -reports from the NOAA (https://www.noaa.gov) and allow access to the -included weather information. - +Version: 1.4 +Summary: Pymetar is a python module and command line tool that fetches and parses METAR reports Home-page: http://www.schwarzvogel.de/software-pymetar.shtml Author: Tobias Klausmann Author-email: klausman-pyme...@schwarzvogel.de License: UNKNOWN -Description: # PyMETAR - a module to fetch and parse METAR reports - - **NOTE:** If you're looking for information regarding Python 2 and PyMETAR, see - the end of this document. - - The National Oceanic and Atmospheric Administration (NOAA, http://www.noaa.gov/) - provides easy access to the weather reports generated by a large number of - weather stations (mostly at airports) worldwide. Those reports are called METAR - reports and are delivered as plain text files that look like this: - - ``` - Duesseldorf, Germany (EDDL) 51-18N 006-46E 41M - Jul 26, 2002 - 03:50 AM EST / 2002.07.26 0850 UTC - Wind: from the SW (220 degrees) at 9 MPH (8 KT):0 - Visibility: 3 mile(s):0 - Sky conditions: mostly cloudy - Weather: mist - Temperature: 60 F (16 C) - Dew Point: 57 F (14 C) - Relative Humidity: 87% - Pressure (altimeter): 30.00 in. Hg (1016 hPa) - ob: EDDL 260850Z 22008KT 5000 BR SCT006 BKN012 16/14 Q1016 BECMG BKN015 - cycle: 9 - ``` - - While this is convenient if you just want to quickly look up the data, there's - some effort involved in parsing all of this into a format that is digestible by - a program. Plus, you have to remember the base URL of the reports and fetch the - file. - - This is what this library does. All you have to do is find the station you're - interested in at http://www.aviationweather.gov/metar and feed the 4-letter - station code to the MetarReport class. - - On the user end, the library provides a large number of methods to fetch the - parsed information in a plethora of formats. Those functions are described in - the file `librarydoc.txt` which was in turn generated using PyDoc. - - PyMETAR uses `urllib2` (and its successors), which in turn makes it easy to honor - the environment variable `http_proxy`. This simplifies use of a proxy - tremendously. Thanks go to Davide Di Blasi for both suggesting and implementing - this. The environment variable is easy to use: just set it to: - - ``` - http://username:passw...@proxy.yourdomain.com:port - ``` - - You can also specify a proxy (with the same syntax) as an argument to the - fetching function. This is sometimes easier when using PyMETAR in a web - application environment (such as ZopeWeatherApplet by Jerome Alet). See - `librarydoc.txt` for details on how to accomplish that. - - You can also use IPs instead of hostnames, of course. When in doubt, ask your - proxy admin. - - Due to some peculiarities in the METAR format, I can not rule out the - possibility that the library barfs on some less common types of reports. If you - encounter such a report, please save it and the error messages you get as - completely as possible and send them to me at `klausman-pyme...@schwarzvogel.de` - --- Thanks a lot! - - Of course you may send all the other bugs you encounter to me, too. As this is a - Python library, chances are that you are Python programmer and can provide a - patch. If you do so, please, by all means use spaces for indentation, four per - level, that makes merging the patch a lot easier. - - ## Python 2, 3, PyPy, PyPy3 and so on - - This version of PyMETAR supports Python 3.x and PyPy3 only. The last version to - have Python2 as its main target was v0.21. - - Compatibility with PyPy (2.x), Jython and other interpreters not mentioned - above is unknown. - - I maintain a branch (called Python2) on git which is the state of Pymetar as it - was for the last version supporting Python 2. That branch is in maintenance - mode, i.e. it will only receive fixes for security issues or bugs that can be - fixed without too much fuss. That branch will go away in 2020. - - [0] http://python-future.org/ - Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) Classifier: Operating System :: OS Independent Description-Content-Type: text/markdown +License-File: COPYING + +# PyMETAR - a module to fetch and parse METAR reports + +**NOTE:** If you're looking for information regarding Python 2 and PyMETAR, see +the end of this document. + +The National Oceanic and Atmospheric Administration (NOAA, http://www.noaa.gov/) +provides easy access to the weather reports generated by a large number of +weather stations (mostly at airports) worldwide. Those reports are called METAR +reports and are delivered as plain text files that look like this: + +``` +Duesseldorf, Germany (EDDL) 51-18N 006-46E 41M +Jul 26, 2002 - 03:50 AM EST / 2002.07.26 0850 UTC +Wind: from the SW (220 degrees) at 9 MPH (8 KT):0 +Visibility: 3 mile(s):0 +Sky conditions: mostly cloudy +Weather: mist +Temperature: 60 F (16 C) +Dew Point: 57 F (14 C) +Relative Humidity: 87% +Pressure (altimeter): 30.00 in. Hg (1016 hPa) +ob: EDDL 260850Z 22008KT 5000 BR SCT006 BKN012 16/14 Q1016 BECMG BKN015 +cycle: 9 +``` + +While this is convenient if you just want to quickly look up the data, there's +some effort involved in parsing all of this into a format that is digestible by +a program. Plus, you have to remember the base URL of the reports and fetch the +file. + +This is what this library does. All you have to do is find the station you're +interested in at http://www.aviationweather.gov/metar and feed the 4-letter +station code to the MetarReport class. + +On the user end, the library provides a large number of methods to fetch the +parsed information in a plethora of formats. Those functions are described in +the file `librarydoc.txt` which was in turn generated using PyDoc. + +PyMETAR uses `urllib2` (and its successors), which in turn makes it easy to honor +the environment variable `http_proxy`. This simplifies use of a proxy +tremendously. Thanks go to Davide Di Blasi for both suggesting and implementing +this. The environment variable is easy to use: just set it to: + +``` +http://username:passw...@proxy.yourdomain.com:port +``` + +You can also specify a proxy (with the same syntax) as an argument to the +fetching function. This is sometimes easier when using PyMETAR in a web +application environment (such as ZopeWeatherApplet by Jerome Alet). See +`librarydoc.txt` for details on how to accomplish that. + +You can also use IPs instead of hostnames, of course. When in doubt, ask your +proxy admin. + +Due to some peculiarities in the METAR format, I can not rule out the +possibility that the library barfs on some less common types of reports. If you +encounter such a report, please save it and the error messages you get as +completely as possible and send them to me at `klausman-pyme...@schwarzvogel.de` +--- Thanks a lot! + +Of course you may send all the other bugs you encounter to me, too. As this is a +Python library, chances are that you are Python programmer and can provide a +patch. If you do so, please, by all means use spaces for indentation, four per +level, that makes merging the patch a lot easier. + +## Python 2, 3, PyPy, PyPy3 and so on + +This version of PyMETAR supports Python 3.x and PyPy3 only. The last version to +have Python2 as its main target was v0.21. + +Compatibility with PyPy (2.x), Jython and other interpreters not mentioned +above is unknown. + +I maintain a branch (called Python2) on git which is the state of Pymetar as it +was for the last version supporting Python 2. That branch is in maintenance +mode, i.e. it will only receive fixes for security issues or bugs that can be +fixed without too much fuss. That branch will go away in 2020. + +[0] http://python-future.org/ + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/pymetar.py new/pymetar-1.4/pymetar.py --- old/pymetar-1.1/pymetar.py 2018-12-25 18:46:34.000000000 +0100 +++ new/pymetar-1.4/pymetar.py 2021-10-15 10:29:50.000000000 +0200 @@ -20,7 +20,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.""" # -import math import re import urllib.request # noqa: E402 @@ -29,12 +28,12 @@ __author__ = "klausman-pyme...@schwarzvogel.de" -__version__ = "1.1" +__version__ = "1.4" CLOUD_RE_STR = (r"^(CAVOK|CLR|SKC|BKN|SCT|FEW|OVC|NSC)([0-9]{3})?" r"(TCU|CU|CB|SC|CBMAM|ACC|SCSL|CCSL|ACSL)?$") -COND_RE_STR = (r"^(-|\\+)?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|" - r"GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\\+?FC)$") +COND_RE_STR = (r"^[-+]?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|" + r"GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\+?FC)$") class EmptyReportException(Exception): @@ -398,14 +397,13 @@ coords = coords + float(elements[0]) - if compass_dir in ('W', 'S'): + if compass_dir in 'WS': coords = -1.0 * coords return coords class WeatherReport: - """Incorporates both the unparsed textual representation of the weather report and the parsed values as soon as they are filled in by ReportParser.""" @@ -493,8 +491,7 @@ """ Return the wind speed in miles per hour. """ - if self.windspeed is not None: - return self.windspeedmph + return self.windspeedmph def getWindSpeedBeaufort(self): """ @@ -502,7 +499,7 @@ cf. https://en.wikipedia.org/wiki/Beaufort_scale """ if self.windspeed is not None: - return round(math.pow(self.windspeed / 0.8359648, 2 / 3.0)) + return round((self.windspeed / 0.8359648) ** (2 / 3.0)) def getWindSpeedKnots(self): """ @@ -528,8 +525,7 @@ """ Return visibility in km. """ - if self.vis is not None: - return self.vis + return self.vis def getVisibilityMiles(self): """ @@ -688,7 +684,7 @@ Return the time when the observation was made in ISO 8601 format (e.g. 2002-07-25 15:12:00Z) """ - return(metar_to_iso8601(self.rtime)) + return metar_to_iso8601(self.rtime) def getPixmap(self): """ @@ -803,12 +799,10 @@ """ matches = self.match_WeatherPart(COND_RE_STR) for wcond in matches: - if ((len(wcond) > 3) and - (wcond.startswith('+') or wcond.startswith('-'))): - + if len(wcond) > 3 and wcond.startswith(('+', '-')): wcond = wcond[1:] - if wcond.startswith('+') or wcond.startswith('-'): + if wcond.startswith(('+', '-')): pphen = 1 elif len(wcond) < 4: pphen = 0 @@ -835,12 +829,13 @@ strings, only the first one is taken into account! """ matches = [] + # FIXME This mixes direct access to the "code" attribute and the accessor function "getRawMetarCode" which just returns "code". if self.Report.code is not None: myre = re.compile(regexp) for wpart in self.Report.getRawMetarCode().split(): match = myre.match(wpart) if match: - matches.append(match.string[match.start(0): match.end(0)]) + matches.append(match.group()) return matches def ParseReport(self, MetarReport=None): @@ -911,14 +906,13 @@ # The line containing date and time of the report # We have to make sure that the station ID is *not* # in this line to avoid trying to parse the ob: line - elif ((data.find(" UTC")) != -1 and - (data.find(self.Report.givenstationid)) == -1): + elif " UTC" in data and self.Report.givenstationid not in data: rtime = data.split("/")[1] self.Report.rtime = rtime.strip() # temperature - elif (header == "Temperature"): + elif header == "Temperature": fnht, cels = data.split(None, 3)[0:3:2] self.Report.tempf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -926,7 +920,7 @@ # wind chill - elif (header == "Windchill"): + elif header == "Windchill": fnht, cels = data.split(None, 3)[0:3:2] self.Report.w_chillf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -934,14 +928,14 @@ # wind dir and speed - elif (header == "Wind"): - if (data.find("Calm") != -1): + elif header == "Wind": + if "Calm" in data: self.Report.windspeed = 0.0 self.Report.windspeedkt = 0.0 self.Report.windspeedmph = 0.0 self.Report.winddir = None self.Report.windcomp = None - elif (data.find("Variable") != -1): + elif "Variable" in data: speed = data.split(" ", 3)[2] self.Report.windspeed = (float(speed) * 0.44704) self.Report.windspeedkt = int(data.split(" ", 5)[4][1:]) @@ -962,7 +956,7 @@ # visibility - elif (header == "Visibility"): + elif header == "Visibility": for visgroup in data.split(): try: self.Report.vis = float(visgroup) * 1.609344 @@ -973,7 +967,7 @@ # dew point - elif (header == "Dew Point"): + elif header == "Dew Point": fnht, cels = data.split(None, 3)[0:3:2] self.Report.dewpf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -981,36 +975,36 @@ # humidity - elif (header == "Relative Humidity"): + elif header == "Relative Humidity": h = data.split("%", 1)[0] self.Report.humid = int(h) # pressure - elif (header == "Pressure (altimeter)"): + elif header == "Pressure (altimeter)": press = data.split(" ", 1)[0] - self.Report.press = (float(press) * 33.863886) + self.Report.press = float(press) * 33.863886 # 1 in = 25.4 mm => 1 inHg = 25.4 mmHg - self.Report.pressmmHg = (float(press) * 25.4000) + self.Report.pressmmHg = float(press) * 25.4000 # shot weather desc. ("rain", "mist", ...) - elif (header == "Weather"): + elif header == "Weather": self.Report.weather = data # short desc. of sky conditions - elif (header == "Sky conditions"): + elif header == "Sky conditions": self.Report.sky = data # the encoded report itself - elif (header == "ob"): + elif header == "ob": self.Report.code = data.strip() # the cycle value ("time slot") - elif (header == "cycle"): + elif header == "cycle": try: self.Report.cycle = int(data) except ValueError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/setup.py new/pymetar-1.4/setup.py --- old/pymetar-1.1/setup.py 2018-12-25 18:48:03.000000000 +0100 +++ new/pymetar-1.4/setup.py 2021-07-06 13:19:31.000000000 +0200 @@ -24,16 +24,13 @@ version=pymetar.__version__, author="Tobias Klausmann", author_email="klausman-pyme...@schwarzvogel.de", - description=pymetar.__doc__, + description="Pymetar is a python module and command line tool that fetches and parses METAR reports", long_description=long_description, long_description_content_type="text/markdown", url="http://www.schwarzvogel.de/software-pymetar.shtml", packages=setuptools.find_packages(), py_modules=["pymetar"], scripts=["bin/pymetar"], - data_files=[("share/doc/pymetar-%s" % pymetar.__version__, - ['README.md', 'COPYING', 'THANKS']), - ("share/man/man1", ['pymetar.1'])], classifiers=( "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/testing/smoketest/pymetar.py new/pymetar-1.4/testing/smoketest/pymetar.py --- old/pymetar-1.1/testing/smoketest/pymetar.py 2018-12-25 18:46:34.000000000 +0100 +++ new/pymetar-1.4/testing/smoketest/pymetar.py 2021-10-15 10:29:50.000000000 +0200 @@ -20,7 +20,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.""" # -import math import re import urllib.request # noqa: E402 @@ -29,12 +28,12 @@ __author__ = "klausman-pyme...@schwarzvogel.de" -__version__ = "1.1" +__version__ = "1.4" CLOUD_RE_STR = (r"^(CAVOK|CLR|SKC|BKN|SCT|FEW|OVC|NSC)([0-9]{3})?" r"(TCU|CU|CB|SC|CBMAM|ACC|SCSL|CCSL|ACSL)?$") -COND_RE_STR = (r"^(-|\\+)?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|" - r"GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\\+?FC)$") +COND_RE_STR = (r"^[-+]?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|" + r"GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\+?FC)$") class EmptyReportException(Exception): @@ -398,14 +397,13 @@ coords = coords + float(elements[0]) - if compass_dir in ('W', 'S'): + if compass_dir in 'WS': coords = -1.0 * coords return coords class WeatherReport: - """Incorporates both the unparsed textual representation of the weather report and the parsed values as soon as they are filled in by ReportParser.""" @@ -493,8 +491,7 @@ """ Return the wind speed in miles per hour. """ - if self.windspeed is not None: - return self.windspeedmph + return self.windspeedmph def getWindSpeedBeaufort(self): """ @@ -502,7 +499,7 @@ cf. https://en.wikipedia.org/wiki/Beaufort_scale """ if self.windspeed is not None: - return round(math.pow(self.windspeed / 0.8359648, 2 / 3.0)) + return round((self.windspeed / 0.8359648) ** (2 / 3.0)) def getWindSpeedKnots(self): """ @@ -528,8 +525,7 @@ """ Return visibility in km. """ - if self.vis is not None: - return self.vis + return self.vis def getVisibilityMiles(self): """ @@ -688,7 +684,7 @@ Return the time when the observation was made in ISO 8601 format (e.g. 2002-07-25 15:12:00Z) """ - return(metar_to_iso8601(self.rtime)) + return metar_to_iso8601(self.rtime) def getPixmap(self): """ @@ -803,12 +799,10 @@ """ matches = self.match_WeatherPart(COND_RE_STR) for wcond in matches: - if ((len(wcond) > 3) and - (wcond.startswith('+') or wcond.startswith('-'))): - + if len(wcond) > 3 and wcond.startswith(('+', '-')): wcond = wcond[1:] - if wcond.startswith('+') or wcond.startswith('-'): + if wcond.startswith(('+', '-')): pphen = 1 elif len(wcond) < 4: pphen = 0 @@ -835,12 +829,13 @@ strings, only the first one is taken into account! """ matches = [] + # FIXME This mixes direct access to the "code" attribute and the accessor function "getRawMetarCode" which just returns "code". if self.Report.code is not None: myre = re.compile(regexp) for wpart in self.Report.getRawMetarCode().split(): match = myre.match(wpart) if match: - matches.append(match.string[match.start(0): match.end(0)]) + matches.append(match.group()) return matches def ParseReport(self, MetarReport=None): @@ -911,14 +906,13 @@ # The line containing date and time of the report # We have to make sure that the station ID is *not* # in this line to avoid trying to parse the ob: line - elif ((data.find(" UTC")) != -1 and - (data.find(self.Report.givenstationid)) == -1): + elif " UTC" in data and self.Report.givenstationid not in data: rtime = data.split("/")[1] self.Report.rtime = rtime.strip() # temperature - elif (header == "Temperature"): + elif header == "Temperature": fnht, cels = data.split(None, 3)[0:3:2] self.Report.tempf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -926,7 +920,7 @@ # wind chill - elif (header == "Windchill"): + elif header == "Windchill": fnht, cels = data.split(None, 3)[0:3:2] self.Report.w_chillf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -934,14 +928,14 @@ # wind dir and speed - elif (header == "Wind"): - if (data.find("Calm") != -1): + elif header == "Wind": + if "Calm" in data: self.Report.windspeed = 0.0 self.Report.windspeedkt = 0.0 self.Report.windspeedmph = 0.0 self.Report.winddir = None self.Report.windcomp = None - elif (data.find("Variable") != -1): + elif "Variable" in data: speed = data.split(" ", 3)[2] self.Report.windspeed = (float(speed) * 0.44704) self.Report.windspeedkt = int(data.split(" ", 5)[4][1:]) @@ -962,7 +956,7 @@ # visibility - elif (header == "Visibility"): + elif header == "Visibility": for visgroup in data.split(): try: self.Report.vis = float(visgroup) * 1.609344 @@ -973,7 +967,7 @@ # dew point - elif (header == "Dew Point"): + elif header == "Dew Point": fnht, cels = data.split(None, 3)[0:3:2] self.Report.dewpf = float(fnht) # The string we have split is "(NN C)", hence the slice @@ -981,36 +975,36 @@ # humidity - elif (header == "Relative Humidity"): + elif header == "Relative Humidity": h = data.split("%", 1)[0] self.Report.humid = int(h) # pressure - elif (header == "Pressure (altimeter)"): + elif header == "Pressure (altimeter)": press = data.split(" ", 1)[0] - self.Report.press = (float(press) * 33.863886) + self.Report.press = float(press) * 33.863886 # 1 in = 25.4 mm => 1 inHg = 25.4 mmHg - self.Report.pressmmHg = (float(press) * 25.4000) + self.Report.pressmmHg = float(press) * 25.4000 # shot weather desc. ("rain", "mist", ...) - elif (header == "Weather"): + elif header == "Weather": self.Report.weather = data # short desc. of sky conditions - elif (header == "Sky conditions"): + elif header == "Sky conditions": self.Report.sky = data # the encoded report itself - elif (header == "ob"): + elif header == "ob": self.Report.code = data.strip() # the cycle value ("time slot") - elif (header == "cycle"): + elif header == "cycle": try: self.Report.cycle = int(data) except ValueError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymetar-1.1/testing/smoketest/runtests.sh new/pymetar-1.4/testing/smoketest/runtests.sh --- old/pymetar-1.1/testing/smoketest/runtests.sh 2018-08-13 19:23:54.000000000 +0200 +++ new/pymetar-1.4/testing/smoketest/runtests.sh 2021-10-15 10:25:44.000000000 +0200 @@ -4,6 +4,7 @@ SETDIR="reports/" SETS="set-2007-10-14 set-2010-01-17 set-2010-10-31 set-2017-07-29" +mkdir -p logs for TEST in $TESTS; do for SET in $SETS; do echo "Running $TEST on $SETDIR/$SET"